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:
- 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.
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 un ref
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.
// 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/vue@2.5.17/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 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 :
[S] ¿debo crear la 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 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.
- 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 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.
alcance de alguna manera events
o stores
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.
Comenzando con el peor de los casos , nuestro componente mezclaría presentación y lógica comercial .
// 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 la 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>
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.
$emit
combinado conv-model
emular.sync
. Creo que deberías seguir el camino de