En la parte 1 de manejo de eventos de Vue, vimos como manejar eventos del DOM y cómo emitir y escuchar eventos personalizados. Esta vez nos vamos a adentrar en un patrón llamado "bus de eventos" y en cómo aprovechar los eventos que disparan los hooks del ciclo de vida de los componentes de Vue.
Bus de eventos
Un bus de eventos es un mecanismo mediante el cual los componentes pueden emitir eventos sin saber quién los va a recibir y recibir eventos sin saber quién los emite. Es un patrón que, en Vue, tiene dos ventajas:
- Un componente puede emitir un evento que sea escuchado por otro que no está relacionado jerárquicamente de ninguna manera, sin que el evento tenga que irse derivando de un componente a otro hasta llegar a su destino. Esto simplifica en gran medida el código, hace que tengamos que escribir mucho menos y produce...
- Mayor desacople de los componentes, en particular en lo relativo a eventos
Sin embargo, el patrón de bus de eventos también es bastante cuestionado, ya que presenta algunas desventajas:
- Si no está bien documentado, a medida que la aplicación crece puede tener una curva de aprendizaje enorme para un nuevo desarrollador no familiarizado con el código.
- Aunque esté bien documentado, es difícil seguir el flujo del código, o mejor dicho, el flujo del código no queda explícito
- No se lleva bien con los tests unitarios, ya que no es fácil testear el bus de eventos.
- Puede conducir a una sobredependencia en los eventos para hacer cosas que es preferible hacer de otras maneras que pueden testearse
Como vemos, este patrón de diseño tiene sus claras ventajas, pero también conviene tener cuidado y ser criterioso a la hora de aplicarlo.
Ejemplo de un bus de eventos
Nada mejor que un ejemplo para ver en qué consiste. Lo primero que debemos hacer es crear el bus de eventos, que no es nada más ni nada menos que un componente. El archivo .vue que lo contiene se verá de la siguiente manera:
import Vue from 'vue';
export const EventBus = new Vue();
Eso es todo. Simple, ¿no? Lo que tenemos acá es un componente que solo importa Vue y se exporta. En esencia, es un componente que está completamente desacoplado del DOM y del resto de la aplicación, y que es muy liviano.
Para utilizarlo, basta con que los demás componentes lo importen y lo usen a la hora de emitir eventos:
<template>
<div class="haceme-click" @click="emitirEventoGlobalDeClick()"></div>
</template>
<script>
// Importar el bus de eventos
import { EventBus } from './event-bus.js';
export default {
data() {
return {
contador: 0
}
},
methods: {
emitirEventoGlobalDeClick() {
this.contador++;
// Emitir el evento en el canal 'me-hicieron-click' con el dato 'contador'
EventBus.$emit('me-hicieron-click', this.contador);
}
}
}
</script>
Luego, para recibir el evento en otro componente, importamos también el bus de eventos y asociamos el canal 'me-hicieron-click' a un método. En este ejemplo, lo hacemos durante el hook del ciclo de vida mounted, por lo que la suscripción al canal se genera justo después de la inserción del componente en el DOM:
import { EventBus } from './event-bus.js';
...
// Tras montar el componente en el DOM, asociamos un método al canal 'me-hicieron-click' del bus de eventos
mounted () {
EventBus.$on('i-got-clicked', this.notificarClick(contador));
},
methods: {
notificarClick: function(contador) {
alert('Ya hicieron click en el botón ' + contador + ' veces.');
}
}
Desuscribir componentes del evento
Para desuscribir suscripciones puntuales a un canal del bus de eventos, podemos poner la siguiente línea en cualquier método del componente:
EventBus.$off('me-hicieron-click', this.notificarClick);
En este caso, solo estamos desuscribiendo la llamada al método "notificarClick" cuando se emite un evento por este canal. Si queremos deshacer todas las suscripciones a este canal, deberíamos escribir:
EventBus.$off('me-hicieron-click');
Por último, si quisiéramos eliminar todas las suscripciones a todos los canales en el bus de eventos, escribiríamos:
EventBus.$off();
Eventos de los hooks del ciclo de vida
En caso de que no tengan muy en claro cuáles son las etapas del ciclo de vida de los componentes de Vue, pueden repasarlas en este diagrama de la documentación oficial.
La manera tradicional de usar los hooks de las diferentes etapas del ciclo de vida es con un método dentro del mismo componente que se dispara al finalizar la etapa correspondiente. A modo de ejemplo, veamos cómo haríamos para disparar un alert en el hook mounted(), que se activa cuando el componente se inserta en el DOM:
<template>
...
</template>
<script>
...
mounted: function() {
alert("Componente montado en el DOM");
}
</script>
Pero ¿qué pasa si queremos que el componente que reaccione al hook no sea el mismo componente que se está montando, sino el que lo contiene? Una posibilidad sería usar el hook mounted para disparar un evento, capturarlo en el componente contenedor y hacer allí lo que sea que tengamos que hacer. Sin embargo, Vue nos proporciona un atajo para hacer eso:
<template>
<componente-hijo @hook:mounted="onMountedComponenteHijo"></componente-hijo>
</template>
<script>
import ComponenteHijo from "./ComponenteHijo";
export default {
components: { ComponenteHijo },
methods: {
onMountedComponenteHijo() {
alert("Componente montado")
}
}
};
</script>
Gracias al listener @hook, podemos escuchar directamente los eventos que emiten los hooks del ciclo de vida y asociarlos a los métodos que deseemos en el componente contenedor. Esto puede ser muy útil si usamos componentes de terceros y queremos reaccionar a las etapas de sus ciclos de vida sin necesidad de modificarlos.
Conclusión
En este artículo, vimos cómo crear un bus de eventos propio sin depender de una librería de terceros, y vimos cómo aprovechar los eventos generados por las etapas del ciclo de vida de los componentes de Vue. ¡Los esperamos en el siguiente artículo para aprender más sobre los patrones de diseño de Vue!