Tipos de comunicação
Ao projetar um aplicativo Vue (ou, na verdade, qualquer aplicativo baseado em componente), existem diferentes tipos de comunicação que dependem de quais preocupações estamos lidando e eles têm seus próprios canais de comunicação.
Lógica de negócios: refere-se a tudo que é específico para seu aplicativo e seu objetivo.
Lógica de apresentação: qualquer coisa com a qual o usuário interage ou que resulte da interação do usuário.
Essas duas preocupações estão relacionadas a estes tipos de comunicação:
- Estado do aplicativo
- Pai-filho
- Pai-filho
- Irmãos
Cada tipo deve usar o canal de comunicação correto.
Canais de comunicação
Canal é um termo vago que usarei para me referir a implementações concretas para trocar dados em torno de um aplicativo Vue.
Adereços: lógica de apresentação pai-filho
O canal de comunicação mais simples no Vue para comunicação direta entre pais e filhos . Deve ser usado principalmente para passar dados relacionados à lógica de apresentação ou um conjunto restrito de dados para baixo na hierarquia.
Refs e métodos: Antipadrão de apresentação
Quando não faz sentido usar um prop para permitir que um filho manipule um evento de um pai, configurar um ref
no componente filho e chamar seus métodos é ótimo.
Não faça isso, é um antipadrão. Repense a arquitetura de seu componente e o fluxo de dados. Se você quiser chamar um método em um componente filho de um pai, provavelmente é hora de levantar o estado ou considerar as outras maneiras descritas aqui ou nas outras respostas.
Eventos: lógica de apresentação filho-pai
$emit
e $on
. O canal de comunicação mais simples para comunicação direta entre pais e filhos. Novamente, deve ser usado para lógica de apresentação.
Ônibus de eventos
A maioria das respostas dá boas alternativas para o bus de eventos, que é um dos canais de comunicação disponíveis para componentes distantes, ou qualquer coisa na verdade.
Isso pode se tornar útil ao passar adereços por todo o lugar, de muito para baixo, até componentes filhos profundamente aninhados, com quase nenhum outro componente precisando deles no meio. Use moderadamente para dados cuidadosamente selecionados.
Tenha cuidado: a criação subsequente de componentes que estão se vinculando ao barramento de eventos serão vinculados mais de uma vez - levando a múltiplos manipuladores acionados e vazamentos. Pessoalmente, nunca senti a necessidade de um ônibus para eventos em todos os aplicativos de página única que projetei no passado.
O seguinte demonstra como um simples erro leva a um vazamento em que o Item
componente ainda dispara, mesmo se removido do 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>
Lembre-se de remover os ouvintes do destroyed
gancho do ciclo de vida.
Loja centralizada (lógica de negócios)
Vuex é o caminho a seguir com Vue para a gestão do estado . Ele oferece muito mais do que apenas eventos e está pronto para ser aplicado em grande escala.
E agora você pergunta :
[S] devo criar a loja da Vuex para cada comunicação secundária?
Realmente brilha quando:
- lidando com sua lógica de negócios,
- comunicar-se com um back-end (ou qualquer camada de persistência de dados, como armazenamento local)
Portanto, seus componentes podem realmente se concentrar nas coisas que devem ser, gerenciando interfaces de usuário.
Isso não significa que você não pode usá-lo para a lógica do componente, mas eu definiria o escopo dessa lógica para um módulo Vuex com namespace com apenas o estado de IU global necessário.
Para evitar lidar com uma grande bagunça em um estado global, o armazenamento deve ser separado em vários módulos com espaço de nomes.
Tipos de componentes
Para orquestrar todas essas comunicações e facilitar a reutilização, devemos pensar nos componentes como dois tipos diferentes.
- Contêineres específicos de aplicativos
- Componentes genéricos
Novamente, isso não significa que um componente genérico deva ser reutilizado ou que um contêiner específico de aplicativo não possa ser reutilizado, mas eles têm responsabilidades diferentes.
Contêineres específicos de aplicativos
Estes são apenas componentes Vue simples que envolvem outros componentes Vue (contêineres genéricos ou outros aplicativos específicos). É aqui que a comunicação da loja Vuex deve acontecer e este contêiner deve se comunicar por outros meios mais simples, como adereços e ouvintes de eventos.
Esses contêineres podem até mesmo não ter nenhum elemento DOM nativo e permitir que os componentes genéricos lidem com os modelos e as interações do usuário.
escopo de alguma forma events
ou stores
visibilidade para componentes irmãos
É aqui que o escopo acontece. A maioria dos componentes não sabe sobre a loja e este componente deve (principalmente) usar um módulo de loja com espaço de nomes com um conjunto limitado de getters
e actions
aplicado com os auxiliares de ligação Vuex fornecidos .
Componentes genéricos
Eles devem receber seus dados de props, fazer alterações em seus próprios dados locais e emitir eventos simples. Na maioria das vezes, eles não deveriam saber que existe uma loja Vuex.
Eles também podem ser chamados de contêineres, pois sua única responsabilidade é o envio para outros componentes da IU.
Comunicação entre irmãos
Então, depois de tudo isso, como devemos nos comunicar entre dois componentes irmãos?
É mais fácil de entender com um exemplo: digamos que temos uma caixa de entrada e seus dados devem ser compartilhados pelo aplicativo (irmãos em locais diferentes na árvore) e persistidos com um back-end.
Começando com o pior cenário , nosso componente mesclaria apresentação e lógica de negócios .
// 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 essas duas questões, devemos envolver nosso componente em um contêiner específico do aplicativo e manter a lógica de apresentação em nosso componente de entrada genérico.
Nosso componente de entrada agora é reutilizável e não conhece o back-end nem os irmãos.
// 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>
Nosso contêiner específico de aplicativo agora pode ser a ponte entre a lógica de negócios e a comunicação da apresentação.
// 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>
Como as ações da loja Vuex lidam com a comunicação de backend, nosso contêiner aqui não precisa saber sobre axios e backend.
$emit
combinado comv-model
para emular.sync
. Eu acho que você deveria seguir o caminho da Vuex