menu

SHARKLABS

Vue Global Event Bus: Use com moderação

/
/
Vue Global Event Bus: Use com moderação
bookmark Vue.js access_time

Comunicação entre Componentes

A comunicação entre componentes Vue.js pode ser feita de várias maneiras. A maneira mais comum é por meio propriedades (do componente pai para o componente filho) e eventos locais (do componente filho para o componente pai).

Caso você tenha uma situação mais complicada, onde seja necessário se comunicar com diversos componentes (que não sejam necessariamente pai ou filho) é recomendável que você use Vuex ou Vue.observable.

Inclusive já escrevi sobre Vuex e Vue.observable. Neste artigo eu falo quais são as características de cada um e quando você deve usá-los.

Basicamente, com Vuex e Vue.observable você compartilha um objeto reativo entre os componentes e você pode monitorar esse objeto por meio de Watchers ou Computed Properties.

Porém, existem situações que você simplesmente precisa chamar um método que está dentro de outro componente. Neste caso você pode emitir um evento global que pode ser ouvido em qualquer lugar do seu software (também chamado de Event Bus).

Global Event Bus: É uma boa prática?

Caso seja necessário você pode usar eventos globais em sua aplicação, mas tenha em mente que isso não é uma boa prática. Inclusive, na documentação do Vue.js este item está inserido na categoria de "Casos Extremos" (Edge Cases). Você pode consultá-lo por meio deste link.

Desta forma, você não deve sair usando isso desenfreadamente, uma vez que seu código pode ficar desorganizado e você também terá muitos problemas com a ordem que esses eventos chegam ao Listener (são eventos assíncronos).

Na minha opinião, essa funcionalidade é útil para rapidamente contornar "grandes problemas". Vale ressaltar que essa "solução rápida" deverá ser refatorada futuramente, preferencialmente usando Vuex ou Vue.observable.

Show me the code

Você pode implementar isso de várias maneiras, mas eu procuro fazer isso da maneira mais simples possível. Basicamente eu uso os seguintes recursos:

  • $root: Para ter acesso à instância raiz do Vue.
  • $emit: Método específico para emitir eventos.
  • $on: Método específico para ouvir eventos.

Este exemplo possui um componente pai e dois componentes filhos. O ouvinte (Listener) do evento ficará localizado dentro de um desses filhos e com a possibilidade de ser chamado de qualquer lugar do seu aplicativo.

<template>
  <div>
    <div>
      <button type="button" @click="call">Call Child2 from Parent</button>
    </div>
    <Child1 />
    <Child2 />
  </div>
</template>

<script>
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';

export default {
  name: 'parent',
  components: { Child1, Child2 },
  methods: {
    call() {
      this.$root.$emit('child2', 'parent');
    },
  },
};
</script>
<template>
  <div>
    <button type="button" @click="call">Call Child2 from Child1</button>
  </div>
</template>

<script>
export default {
  name: 'Child1',
  methods: {
    call() {
      this.$root.$emit('child2', 'child1');
    },
  },
};
</script>
<template>
  <div>Incoming Calls to Child2: {{ incomingCalls }}</div>
</template>

<script>
export default {
  name: 'Child2',
  data: () => ({
    incomingCalls: [],
  }),
  mounted() {
    this.$root.$on('child2', (from) => this.incomingCalls.push(from));
  },
};
</script>

O código é muito simples. Porém, como falei anteriormente, use com moderação!

Atualização Outubro de 2020: Eventos com Vue 3

Em 2020 a versão 3 do Vue.js foi lançada e tivemos algumas modificações na API de eventos. A partir de agora não tem mais suporte para eventos globais.

Seguindo o Guia de Migração isso pode contornado com a biblioteca mitt. Primeiramente vamos instalá-la:

npm install mitt --save

Agora vamos criar um plugin dentro do Vue.js para disponbilizar o mitt em toda a aplicação. Crie o arquivo emitter.js:

import mitt from 'mitt';

let emitter = mitt();

export default function (app, options) {
  app.config.globalProperties.$emitter = emitter;
}

Agora modifique o arquivo main.js:

import { createApp } from 'vue';
import App from './App.vue';
import emitterPlugin from './emitter.js'; // ATTENTION HERE

let app = createApp(App);
app.use(emitterPlugin); // ATTENTION HERE
app.mount('#app');

Feito isso podemos executar o comando this.$emitter dentro de nossos componentes. Veja como fica o exemplo que fiz anteriormente, agora com a versão 3 do Vue.js:

<template>
  <div>
    <div>
      <button type="button" @click="call">Call Child2 from Parent</button>
    </div>
    <Child1 />
    <Child2 />
  </div>
</template>

<script>
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';

export default {
  name: 'Parent',
  components: { Child1, Child2 },
  methods: {
    call() {
      this.$emitter.emit('child2', 'parent');
    },
  },
};
</script>
<template>
  <div>
    <button type="button" @click="call">Call Child2 from Child1</button>
  </div>
</template>

<script>
export default {
  name: 'Child1',
  methods: {
    call() {
      this.$emitter.emit('child2', 'child1');
    },
  },
};
</script>
<template>
  <div>Incoming Calls to Child2: {{ incomingCalls }}</div>
</template>

<script>
export default {
  name: 'Child2',
  data: () => ({
    incomingCalls: [],
  }),
  mounted() {
    this.$emitter.on('child2', (from) => this.incomingCalls.push(from));
  },
};
</script>

Dúvidas ou sugestões é só entrar em contato. Abraço.

Autor
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Martin Fowler