Visão geral
No Vue.js 2.x, model.sync
será preterido .
Então, qual é a maneira adequada de se comunicar entre componentes irmãos no Vue.js 2.x ?
fundo
Pelo que entendi Vue 2.x, o método preferido para comunicação entre irmãos é usar uma loja ou um barramento de evento .
De acordo com Evan (criador do Vue):
Também vale a pena mencionar que "passar dados entre componentes" geralmente é uma má ideia, porque no final o fluxo de dados se torna impossível de rastrear e muito difícil de depurar.
Se um dado precisa ser compartilhado por vários componentes, prefira lojas globais ou Vuex .
E:
.once
e.sync
estão obsoletos. Os adereços agora estão sempre em uma direção para baixo. Para produzir efeitos colaterais no escopo pai, um componente precisa explicitamenteemit
um evento em vez de depender de vinculação implícita.
Portanto, Evan sugere o uso de $emit()
e $on()
.
Preocupações
O que me preocupa é:
- Cada um
store
eevent
tem uma visibilidade global (corrija-me se estiver errado); - É muito desperdício criar uma nova loja para cada comunicação secundária;
O que eu quero é algum escopo events
ou stores
visibilidade para componentes irmãos. (Ou talvez eu não tenha entendido a ideia acima.)
Questão
Então, qual é a maneira correta de se comunicar entre componentes irmãos?
fonte
$emit
combinado comv-model
para emular.sync
. Eu acho que você deveria seguir o caminho da VuexRespostas:
Com o Vue 2.0, estou usando o mecanismo eventHub conforme demonstrado na documentação .
Defina o hub de eventos centralizado.
Agora, em seu componente, você pode emitir eventos com
E para ouvir você faz
Atualização Veja a resposta de @alex , que descreve uma solução mais simples.
fonte
this.$root.$emit()
ethis.$root.$on()
Você pode até mesmo torná-lo mais curto e usar a
Vue
instância raiz como Hub de eventos global:Componente 1:
Componente 2:
fonte
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:
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 umref
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.Exibir trecho de código
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 :
Realmente brilha quando:
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.
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.
É 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
eactions
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 .
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.
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.
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.
fonte
Ok, podemos nos comunicar entre irmãos via pais usando
v-on
eventos.Vamos supor que desejamos atualizar o
Details
componente ao clicar em algum elementoList
.no
Parent
:Modelo:
Aqui:
v-on:select-item
é um evento, que será chamado emList
componente (veja abaixo);setSelectedItem
é umParent
método de atualizaçãoselectedModel
;JS:
No
List
:Modelo:
JS:
Aqui:
this.$emit('select-item', item)
enviará o item viaselect-item
diretamente no pai. E os pais irão enviar para aDetails
vistafonte
O que normalmente faço se quero "hackear" os padrões normais de comunicação no Vue, especialmente agora que
.sync
está obsoleto, é criar um EventEmitter simples que lida com a comunicação entre os componentes. De um dos meus projetos mais recentes:Com este
Transmitter
objeto você pode fazer, em qualquer componente:E para criar um componente de "recebimento":
Novamente, isso é para usos realmente específicos. Não baseie todo o seu aplicativo neste padrão, use algo como
Vuex
.fonte
vuex
, mas, novamente, devo criar a loja da vuex para cada comunicação menor?vuex
sim, vá em frente. Use-o.Como lidar com a comunicação entre irmãos depende da situação. Mas, primeiro, quero enfatizar que a abordagem de ônibus de eventos globais está indo embora no Vue 3 . Veja este RFC . Por isso decidi escrever uma nova resposta.
Padrão Ancestral Comum Mais Baixo (ou “LCA”)
Para casos simples, eu recomendo fortemente o uso do padrão de Ancestral Comum Mais Baixo (também conhecido como “dados para baixo, eventos para cima”). Esse padrão é fácil de ler, implementar, testar e depurar.
Em essência, isso significa que se dois componentes precisam se comunicar, coloque seu estado compartilhado no componente mais próximo que ambos compartilham como ancestrais. Passe dados do componente pai para o componente filho por meio de adereços e passe informações do filho para o pai emitindo um evento (veja um exemplo disso no final desta resposta).
Para um exemplo artificial, em um aplicativo de e-mail, se o componente "Para" precisasse interagir com o componente "corpo da mensagem", o estado dessa interação poderia residir em seu pai (talvez um componente chamado
email-form
). Você pode ter um prop noemail-form
chamadoaddressee
para que o corpo da mensagem possa preceder automaticamenteDear {{addressee.name}}
o e-mail com base no endereço de e-mail do destinatário.O LCA torna-se oneroso se a comunicação tiver que viajar longas distâncias com muitos componentes intermediários. Costumo referir colegas para este excelente post de blog . (Ignore o fato de que seus exemplos usam Ember; suas ideias são aplicáveis em muitos frameworks de IU.)
Padrão de contêiner de dados (por exemplo, Vuex)
Para casos ou situações complexas em que a comunicação entre pais e filhos envolveria muitos intermediários, use o Vuex ou uma tecnologia de contêiner de dados equivalente. Quando apropriado, use módulos com namespace .
Por exemplo, pode ser razoável criar um namespace separado para uma coleção complexa de componentes com muitas interconexões, como um componente de calendário completo.
Padrão Publicar / Assinar (Event Bus)
Se o padrão de barramento de evento (ou “publicar / assinar”) for mais apropriado para suas necessidades, a equipe principal do Vue agora recomenda o uso de uma biblioteca de terceiros, como o mitt . (Consulte o RFC mencionado no parágrafo 1.)
Divagações bônus e código
Aqui está um exemplo básico da solução de menor ancestral comum para a comunicação de irmão para irmão, ilustrado por meio do jogo whack-a-mole .
Uma abordagem ingênua pode ser pensar, “a toupeira 1 deve dizer à toupeira 2 para aparecer depois de ser destruída”. Mas Vue desencoraja esse tipo de abordagem, uma vez que quer que pensemos em termos de estruturas de árvore .
Isso provavelmente é uma coisa muito boa. Um aplicativo não trivial em que os nós se comunicam diretamente entre si através das árvores DOM seria muito difícil de depurar sem algum tipo de sistema de contabilidade (como o Vuex fornece). Além disso, os componentes que usam "down data, events up" tendem a exibir baixo acoplamento e alta reutilização - ambas características altamente desejáveis que ajudam a escalar aplicativos grandes.
Neste exemplo, quando uma toupeira é atingida, ela emite um evento. O componente gerenciador de jogo decide qual é o novo estado do aplicativo e, portanto, o irmão mole sabe o que fazer implicitamente depois que o Vue é renderizado novamente. É um exemplo trivial de “ancestral comum mais baixo”.
fonte