javascript - variable - Comunicación entre componentes hermanos en VueJs 2.0
vuex read state (5)
Con Vue 2.0, estoy usando el mecanismo eventHub como se demuestra en la documentation .
-
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 su componente puede emitir eventos con
this.eventHub.$emit(''update'', data)
-
Y para escuchar lo haces
this.eventHub.$on(''update'', data => { // do your thing })
Actualización Consulte la respuesta de @alex , que describe una solución más simple.
En vuejs 2.0
model.sync
quedará en
deprecated
.
Entonces, ¿cuál es una forma adecuada de comunicarse entre los componentes hermanos en vuejs 2.0 ?
Como entiendo, la idea en Vue 2.0 es tener comunicación entre hermanos mediante el uso de una tienda o un bus de eventos .
Según evan :
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 indescifrable y muy difícil de depurar.
Si una pieza de datos necesita ser compartida por múltiples componentes, prefiera tiendas globales o Vuex .
Y:
.sync
y.sync
están en desuso. Los accesorios ahora son siempre unidireccionales. Para producir efectos secundarios en el ámbito primario, un componente necesitaemit
explícitamente un evento en lugar de confiar en el enlace implícito.
(Entonces, él
suggest
usar
$emit
y
$on
)
Estoy preocupado por:
-
Cada
store
yevent
tiene una visibilidad global (corrígeme si me equivoco); - Es demasiado crear una nueva tienda para cada comunicación menor;
Lo que quiero es determinar de alguna manera los
events
o la visibilidad de las
stores
para los componentes hermanos.
O tal vez no entendí la idea.
Entonces, ¿cómo comunicarse de una manera correcta?
De acuerdo, podemos comunicarnos entre hermanos a través de los padres mediante eventos
v-on
.
Parent
|-List of items //sibling 1 - "List"
|-Details of selected item //sibling 2 - "Details"
Supongamos que queremos actualizar el componente
Details
cuando hacemos clic en algún elemento de la
List
.
en
Parent
:
Modelo:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Aquí:
-
v-on:select-item
es un evento, que se llamará en el componenteList
(ver más abajo); -
setSelectedItem
es un método deParent
para actualizarselectedModel
;
JS:
//...
data () {
return {
listModel: [''a'', ''b'']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item //here we change the Detail''s model
},
}
//...
En la
List
:
Modelo:
<ul>
<li v-for="i in list"
:value="i"
@click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JS:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit(''select-item'', item) // here we call the event we waiting for in "Parent"
},
}
//...
Aquí:
-
this.$emit(''select-item'', item)
enviará el artículo a través deselect-item
directamente en el padre. Y los padres lo enviarán a la vistaDetails
Lo que generalmente hago si quiero "piratear" los patrones normales de comunicación en Vue, especialmente ahora que
.sync
está en desuso, es crear un EventEmitter simple que maneje la comunicación entre los componentes.
De uno de mis últimos proyectos:
import {EventEmitter} from ''events''
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
Con este objeto
Transmitter
puede hacer, en cualquier componente:
import Transmitter from ''./Transmitter''
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit(''update'')
}
})
Y para crear un componente "receptor":
import Transmitter from ''./Transmitter''
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on(''update'', this.doThingOnUpdate)
}
})
Nuevamente, esto es para usos realmente específicos.
No base toda su aplicación en este patrón, use algo como
Vuex
en
Vuex
lugar.
Sé que esta es una vieja pregunta, pero quería exponer otros canales de comunicación y cómo ver la aplicación y las comunicaciones desde una perspectiva superior.
Tipos de comunicación
Lo primero que hay que entender al diseñar una aplicación Vue (o, de hecho, cualquier aplicación basada en componentes) es que existen diferentes tipos de comunicación que dependen de las preocupaciones con las que estamos tratando y que necesitan 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 el usuario interactúa o que resulta de la interacción del usuario.
Estas dos preocupaciones están relacionadas con estos tipos de comunicación:
- Estado de la solicitud
- Padre-hijo
- Padre-hijo
- Hermanos
Cada tipo debe usar el canal de comunicación correcto.
Canales de comunicación
Un canal es un término suelto que usaré para referirme a implementaciones concretas para intercambiar datos alrededor de una aplicación Vue.
Atrezzo (lógica de presentación)
El canal de comunicación más simple en Vue para la comunicación directa entre padres e hijos . Debe usarse principalmente para pasar datos relacionados con la lógica de presentación o un conjunto restringido de datos por la jerarquía.
Refs y métodos (lógica de presentación)
Cuando no tiene sentido usar un accesorio para permitir que un niño maneje un evento de un padre,
configurar una
ref
en el componente hijo y llamar a sus métodos
está bien.
Algunas personas pueden decir que este es un acoplamiento estrecho entre el padre y el hijo, pero es el mismo acoplamiento que el del uso de accesorios. Si podemos acordar un contrato para accesorios, también podemos acordar un contrato para métodos.
Eventos (lógica de presentación)
$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 (ambos)
La mayoría de las respuestas dan 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 ser útil al pasar accesorios por todo el lugar, desde componentes secundarios muy anidados, casi sin otros componentes que los necesiten en el medio.
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á múltiples controladores activados y fugas. Personalmente, nunca sentí la necesidad de un bus de eventos en todas las aplicaciones de una sola página que he diseñado en el pasado.
A continuación se demuestra cómo un simple error conduce a una fuga donde el componente
Item
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 los oyentes en el gancho
destroyed
ciclo de vida.
Tienda centralizada (lógica de negocios)
Vuex es el camino a seguir con Vue para la gestión del estado . Ofrece mucho más que solo eventos y está listo para la aplicación a gran escala.
Y ahora preguntas :
[S] ¿debería crear la tienda de vuex para cada comunicación menor?
Realmente brilla cuando:
- lidiando con su lógica de negocios,
- comunicarse con un backend
Por lo tanto, sus componentes realmente pueden centrarse en las cosas que deben ser, administrando las interfaces de usuario.
No significa que no pueda usarlo para la lógica de componentes, pero yo incluiría esa lógica en un módulo Vuex de 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, debemos dividir la tienda en múltiples módulos de 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.
- Contenedores específicos de aplicaciones
- Componentes genéricos
Nuevamente, no significa que un componente genérico deba reutilizarse o que un contenedor específico de la aplicación no pueda reutilizarse, pero tienen diferentes responsabilidades.
Contenedores específicos de aplicaciones
Estos son solo componentes simples de Vue que envuelven otros componentes de Vue (contenedores genéricos u otros de aplicaciones específicas). Aquí es donde debe ocurrir la comunicación de la tienda Vuex y este contenedor debe comunicarse a través de otros medios más simples como accesorios y oyentes de eventos.
Estos contenedores incluso podrían no tener elementos DOM nativos y dejar que los componentes genéricos se encarguen de esto.
Alcance de alguna manera
events
ostores
visibilidad para los 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
aplicadas con los mapeadores Vuex provistos.
Componentes genéricos
Estos deberían 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 llamarse 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 debemos 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 back-end.
Comenzando con el peor de los casos , nuestro componente combinaría presentación y lógica empresarial .
// 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 aplicaciones 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>
Dado que las acciones de la tienda Vuex se ocupan de la comunicación de back-end, nuestro contenedor aquí no necesita saber sobre axios y el back-end.