Solución:
Incluso puedes acortarlo y usar la raíz. Vue
instancia como Event Hub global:
Componente 1:
this.$root.$emit('eventing', data);
Componente 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
Con Vue.js 2.0, estoy usando el mecanismo eventHub como se muestra en la documentación.
-
Definir centro de eventos centralizado.
const eventHub = new Vue() // Single event hub // Distribute to components using global mixin Vue.mixin({ data: function () { return { eventHub: eventHub } } })
-
Ahora en tu componente puedes emitir eventos con
this.eventHub.$emit('update', data)
-
Y escucharte lo haces
this.eventHub.$on('update', data => { // do your thing })
Actualizar
Consulte la respuesta de alex, que describe una solución más sencilla.
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 de negocios: se refiere a todo lo específico de su aplicación y su objetivo.
Lógica de presentación: cualquier cosa con la que el usuario interactúe o que resulte de la interacción del usuario.
Estas dos preocupaciones están relacionadas con estos tipos de comunicación:
- Estado de la aplicación
- Padre-hijo
- Hijo-padre
- Hermanos
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.
Props: lógica de presentación de padres e hijos
El canal de comunicación más simple en Vue para directo Padre-hijo comunicación. 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 un ref
en el componente hijo 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 el momento de levantar el estado o considere 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, debe usarse para la lógica de presentación.
Bus de eventos
La mayoría de las respuestas brindan buenas alternativas para el bus de eventos, que es uno de los canales de comunicación disponibles para componentes distantes, o cualquier cosa de hecho.
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.
Ten 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 múltiples 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 donde el Item
El componente aún se activa incluso si se elimina del DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<div id="app">
<button type="button" @click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Recuerde eliminar a los oyentes en el destroyed
gancho del ciclo de vida.
Tienda centralizada (lógica empresarial)
Vuex es el camino a seguir con Vue para administración del Estado. Ofrece mucho más que eventos y está listo para su aplicación a gran escala.
Y ahora preguntas:
[S]¿Debo crear una tienda de vuex para cada comunicación menor?
Realmente brilla cuando:
- lidiar con la lógica de su negocio,
- comunicarse con un backend (o cualquier capa de persistencia de datos, como el almacenamiento local)
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 la interfaz de usuario 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, deberíamos pensar en los componentes como dos tipos diferentes.
- Contenedores específicos de la aplicación
- Componentes genéricos
Nuevamente, no significa que un componente genérico deba reutilizarse o que el contenedor específico de una aplicación no se pueda reutilizar, 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.
alcance de alguna manera
events
ostores
visibilidad para componentes hermanos
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
y actions
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.
Empezando por el peor de los casos, nuestro componente mezclaría presentación y negocio lógica.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
})
.then((response) => {
this.$root.$emit('update', response.data);
});
}
}
}
</script>
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.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Nuestro contenedor específico de aplicación ahora puede ser el puente entre la lógica empresarial y la comunicación de presentación.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" @change="updateState"></my-input>
<my-input :initial="otherValue" @change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
@click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '@/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.updateState,
])
}
</script>
Desde la tienda Vuex comportamiento lidiar con la comunicación de backend, nuestro contenedor aquí no necesita saber acerca de axios y el backend.