Visión general
En Vue.js 2.x, model.sync
quedará obsoleto .
Entonces, ¿cuál es una forma adecuada de comunicarse entre componentes hermanos en Vue.js 2.x ?
Antecedentes
Según tengo entendido Vue 2.x, el método preferido para la comunicación entre hermanos es usar una tienda o un bus de eventos .
Según Evan (creador de Vue):
También vale la pena mencionar que "pasar datos entre componentes" es generalmente una mala idea, porque al final el flujo de datos se vuelve imposible de rastrear y muy difícil de depurar.
Si un dato necesita ser compartido por múltiples componentes, prefiera tiendas globales o Vuex .
Y:
.once
y.sync
están en desuso. Los accesorios ahora son siempre unidireccionales. Para producir efectos secundarios en el ámbito principal, un componente necesita explícitamenteemit
un evento en lugar de depender de un enlace implícito.
Entonces, Evan sugiere usar $emit()
y $on()
.
Preocupaciones
Lo que me preocupa es:
- Cada uno
store
yevent
tiene una visibilidad global (corríjanme si me equivoco); - Es un desperdicio crear una nueva tienda para cada comunicación menor;
Lo que quiero es algún alcance events
o stores
visibilidad para los componentes de los hermanos. (O quizás no entendí la idea anterior).
Pregunta
Entonces, ¿cuál es la forma correcta de comunicarse entre componentes hermanos?
fuente
$emit
combinado conv-model
emular.sync
. Creo que deberías seguir el camino deRespuestas:
Con Vue 2.0, estoy usando el mecanismo eventHub como se muestra en la documentación .
Definir centro de eventos centralizado.
Ahora en tu componente puedes emitir eventos con
Y escucharte lo haces
Actualización Consulte la respuesta de @alex , que describe una solución más sencilla.
fuente
this.$root.$emit()
ythis.$root.$on()
Incluso puede acortarlo y usar la
Vue
instancia raíz como Event Hub global:Componente 1:
Componente 2:
fuente
Tipos de comunicación
Al diseñar una aplicación Vue (o de hecho, cualquier aplicación basada en componentes), existen diferentes tipos de comunicación que dependen de las preocupaciones que estemos tratando y tienen sus propios canales de comunicación.
Lógica empresarial: se refiere a todo lo específico de su aplicación y su objetivo.
Lógica de presentación: cualquier cosa con la que interactúe el usuario o que resulte de la interacción del usuario.
Estas dos preocupaciones están relacionadas con estos tipos de comunicación:
Cada tipo debe utilizar el canal de comunicación correcto.
Canales de comunicación
Un canal es un término vago que usaré para referirme a implementaciones concretas para intercambiar datos en torno a una aplicación Vue.
Accesorios: lógica de presentación de padres e hijos
El canal de comunicación más simple de Vue para la comunicación directa entre padres e hijos . Debería utilizarse principalmente para pasar datos relacionados con la lógica de presentación o un conjunto restringido de datos hacia abajo en la jerarquía.
Refs y métodos: Presentación anti-patrón
Cuando no tiene sentido usar un accesorio para permitir que un niño maneje un evento de un padre, configurar unref
componente secundario y llamar a sus métodos está bien.No hagas eso, es un anti-patrón. Reconsidere la arquitectura de sus componentes y el flujo de datos. Si desea llamar a un método en un componente secundario de un padre, probablemente sea hora de elevar el estado o considerar las otras formas descritas aquí o en las otras respuestas.
Eventos: lógica de presentación hijo-padre
$emit
y$on
. El canal de comunicación más simple para la comunicación directa entre padres e hijos. Nuevamente, debería usarse para la lógica de presentación.Bus de eventos
La mayoría de las respuestas ofrecen buenas alternativas para el bus de eventos, que es uno de los canales de comunicación disponibles para componentes distantes, o cualquier otra cosa.
Esto puede resultar útil cuando se pasan accesorios por todas partes, desde muy arriba hacia abajo hasta componentes secundarios profundamente anidados, sin que casi ningún otro componente los necesite en el medio. Úselo con moderación para datos cuidadosamente seleccionados.
Tenga cuidado: la creación posterior de componentes que se unen al bus de eventos se vinculará más de una vez, lo que provocará que se activen varios controladores y se produzcan fugas. Personalmente, nunca sentí la necesidad de un bus de eventos en todas las aplicaciones de una sola página que diseñé en el pasado.
A continuación se demuestra cómo un simple error conduce a una fuga en la que el
Item
componente aún se activa incluso si se elimina del DOM.Mostrar fragmento de código
Recuerde eliminar los oyentes en el
destroyed
enlace del ciclo de vida.Tienda centralizada (lógica empresarial)
Vuex es el camino a seguir con Vue para la gestión estatal . Ofrece mucho más que eventos y está listo para su aplicación a gran escala.
Y ahora preguntas :
Realmente brilla cuando:
Por lo tanto, sus componentes realmente pueden enfocarse en las cosas que deben ser, administrar interfaces de usuario.
No significa que no pueda usarlo para la lógica de componentes, pero limitaría esa lógica a un módulo Vuex con espacio de nombres con solo el estado de IU global necesario.
Para evitar lidiar con un gran lío de todo en un estado global, la tienda debe estar separada en varios módulos con espacios de nombres.
Tipos de componentes
Para orquestar todas estas comunicaciones y facilitar la reutilización, debemos pensar en los componentes como dos tipos diferentes.
Nuevamente, no significa que un componente genérico deba reutilizarse o que el contenedor específico de una aplicación no pueda reutilizarse, pero tienen diferentes responsabilidades.
Contenedores específicos de la aplicación
Estos son solo componentes de Vue simples que envuelven otros componentes de Vue (contenedores genéricos u otros contenedores específicos de la aplicación). Aquí es donde debería ocurrir la comunicación de la tienda Vuex y este contenedor debería comunicarse a través de otros medios más simples como accesorios y oyentes de eventos.
Estos contenedores podrían incluso no tener ningún elemento DOM nativo y dejar que los componentes genéricos se ocupen de las plantillas y las interacciones del usuario.
Aquí es donde ocurre el alcance. La mayoría de los componentes no conocen la tienda y este componente debería (en su mayoría) usar un módulo de tienda con espacio de nombres con un conjunto limitado de
getters
yactions
aplicado con los ayudantes de enlace Vuex proporcionados .Componentes genéricos
Estos deben recibir sus datos de accesorios, realizar cambios en sus propios datos locales y emitir eventos simples. La mayoría de las veces, no deberían saber que existe una tienda Vuex.
También podrían denominarse contenedores, ya que su única responsabilidad podría ser enviar a otros componentes de la interfaz de usuario.
Comunicación entre hermanos
Entonces, después de todo esto, ¿cómo deberíamos comunicarnos entre dos componentes hermanos?
Es más fácil de entender con un ejemplo: digamos que tenemos un cuadro de entrada y sus datos deben compartirse en la aplicación (hermanos en diferentes lugares del árbol) y persistir con un backend.
Comenzando con el peor de los casos , nuestro componente mezclaría presentación y lógica comercial .
Para separar estas dos preocupaciones, debemos envolver nuestro componente en un contenedor específico de la aplicación y mantener la lógica de presentación en nuestro componente de entrada genérico.
Nuestro componente de entrada ahora es reutilizable y no conoce el backend ni los hermanos.
Nuestro contenedor específico de la aplicación ahora puede ser el puente entre la lógica empresarial y la comunicación de presentación.
Dado que las acciones de la tienda Vuex se ocupan de la comunicación de backend, nuestro contenedor aquí no necesita conocer axios y el backend.
fuente
Bien, podemos comunicarnos entre hermanos a través de los padres mediante
v-on
eventos.Supongamos que queremos actualizar el
Details
componente cuando hacemos clic en algún elementoList
.en
Parent
:Modelo:
Aquí:
v-on:select-item
es un evento, que se llamará en elList
componente (ver más abajo);setSelectedItem
es elParent
método de a para actualizarselectedModel
;JS:
En
List
:Modelo:
JS:
Aquí:
this.$emit('select-item', item)
enviará el artículoselect-item
directamente a los padres. Y el padre lo enviará a laDetails
vista.fuente
Lo que suelo hacer si quiero "piratear" los patrones normales de comunicación en Vue, especialmente ahora que
.sync
está obsoleto, es crear un EventEmitter simple que maneje la comunicación entre componentes. De uno de mis últimos proyectos:Con este
Transmitter
objeto puede hacer, en cualquier componente:Y para crear un componente de "recepción":
Nuevamente, esto es para usos realmente específicos. No base toda su aplicación en este patrón, use algo como
Vuex
.fuente
vuex
, pero nuevamente, ¿debo crear la tienda de vuex para cada comunicación menor?vuex
sí, hágalo. Úselo.Cómo manejar la comunicación entre hermanos depende de la situación. Pero primero quiero enfatizar que el enfoque de bus de eventos global está desapareciendo en Vue 3 . Vea este RFC . De ahí por qué decidí escribir una nueva respuesta.
Patrón de antepasado común más bajo (o "LCA")
Para casos simples, recomiendo encarecidamente utilizar el patrón de ancestro común más bajo (también conocido como "datos caídos, eventos arriba"). Este patrón es fácil de leer, implementar, probar y depurar.
En esencia, esto significa que si dos componentes necesitan comunicarse, coloque su estado compartido en el componente más cercano que ambos comparten como antepasados. Pase datos del componente principal al componente secundario a través de accesorios y pase información de un elemento secundario a otro mediante la emisión de un evento (vea un ejemplo de esto en la parte inferior de esta respuesta).
Para un ejemplo artificial, en una aplicación de correo electrónico, si el componente "Para" necesitaba interactuar con el componente "cuerpo del mensaje", el estado de esa interacción podría vivir en su padre (tal vez un componente llamado
email-form
). Es posible que tenga un accesorio en elemail-form
llamadoaddressee
para que el cuerpo del mensaje pueda anteponerse automáticamenteDear {{addressee.name}}
al correo electrónico según la dirección de correo electrónico del destinatario.El LCA se vuelve oneroso si la comunicación tiene que viajar largas distancias con muchos componentes intermediarios. A menudo recomiendo a mis colegas esta excelente publicación de blog . (Ignore el hecho de que sus ejemplos usan Ember; sus ideas son aplicables en muchos marcos de interfaz de usuario).
Patrón de contenedor de datos (p. Ej., Vuex)
Para casos complejos o situaciones en las que la comunicación entre padres e hijos involucraría a demasiados intermediarios, utilice Vuex o una tecnología de contenedor de datos equivalente. Cuando sea apropiado, use módulos con espacio de nombres .
Por ejemplo, podría ser razonable crear un espacio de nombres separado para una colección compleja de componentes con muchas interconexiones, como un componente de calendario con todas las funciones.
Patrón de publicación / suscripción (bus de eventos)
Si el patrón de bus de eventos (o "publicar / suscribirse") es más apropiado para sus necesidades, el equipo central de Vue ahora recomienda usar una biblioteca de terceros como mitt . (Consulte el RFC mencionado en el párrafo 1.)
Divagaciones y código extra
A continuación, se muestra un ejemplo básico de la solución Lowest Common Ancestor para la comunicación entre hermanos, ilustrada mediante el juego whack-a-mole .
Un enfoque ingenuo podría ser pensar, "el topo 1 debería decirle al topo 2 que aparezca después de ser golpeado". Pero Vue desalienta este tipo de enfoque, ya que quiere que pensemos en términos de estructuras de árboles .
Probablemente esto sea algo muy bueno. Una aplicación no trivial donde los nodos se comunican directamente entre sí a través de árboles DOM sería muy difícil de depurar sin algún tipo de sistema de contabilidad (como lo proporciona Vuex). Además de eso, los componentes que usan "datos inactivos, eventos arriba" tienden a exhibir un bajo acoplamiento y una alta capacidad de reutilización, ambos rasgos muy deseables que ayudan a escalar las aplicaciones grandes.
En este ejemplo, cuando un lunar es golpeado, emite un evento. El componente del administrador del juego decide cuál es el nuevo estado de la aplicación y, por lo tanto, el hermano topo sabe qué hacer implícitamente después de que Vue vuelva a renderizar. Es un ejemplo algo trivial del "antepasado común más bajo".
fuente