Un bus de eventos es un patrón de diseño (y aunque aquí hablaremos de JavaScript, es un patrón de diseño en cualquier lenguaje) que se puede utilizar para simplificar las comunicaciones entre diferentes componentes. También se puede considerar publicar / suscribir o pubsub.
La idea es que los componentes puedan escuchar el bus de eventos para saber cuándo hacer las cosas que hacen. Por ejemplo, un componente de “panel de pestañas” puede escuchar eventos que le indiquen que cambie la pestaña activa. Claro, eso puede suceder con un clic en una de las pestañas y, por lo tanto, se maneja completamente dentro de ese componente. Pero con un bus de eventos, algunos otros elementos podrían indicarle a la pestaña que cambie. Imagine el envío de un formulario que provoca un error que debe alertar al usuario dentro de una pestaña específica, por lo que el formulario envía un mensaje al bus de eventos diciéndole al componente de pestañas que cambie la pestaña activa a la que tiene el error. Eso es lo que parece a bordo de un autobús de eventos.
El pseudocódigo para esa situación sería como …
// Tab Component
Tabs.changeTab = id => {
// DOM work to change the active tab.
}
MyEventBus.subscribe("change-tab", Tabs.changeTab(id));
// Some other component...
// something happens, then:
MyEventBus.publish("change-tab", 2);
¿Necesita una biblioteca de JavaScript para esto? (Pregunta capciosa: nunca necesitas una biblioteca de JavaScript). Bueno, hay muchas opciones disponibles:
- PubSubJS
- EventEmitter3
- Postal.js
- jQuery incluso admitía eventos personalizados, que está muy relacionado con este patrón.
Además, echa un vistazo a Mitt, que es una biblioteca que solo tiene 200 bytes comprimidos en gzip. Hay algo en este patrón simple que inspira a las personas a abordarlo ellos mismos de la manera más sucinta posible.
¡Hagámoslo nosotros mismos! No usaremos ninguna biblioteca de terceros y aprovecharemos un sistema de escucha de eventos que ya está integrado en JavaScript con el addEventListener
todos conocemos y amamos.
Primero, un poco de contexto
El addEventListener
API en JavaScript es una función miembro de la EventTarget
clase. La razón por la que podemos unir un click
evento a un botón se debe a que la interfaz prototipo de <button>
(HTMLButtonElement
) hereda de EventTarget
indirectamente.
Fuente: MDN Web Docs
A diferencia de la mayoría de las otras interfaces DOM, EventTarget
se puede crear directamente usando el new
palabra clave. Es compatible con todos los navegadores modernos, pero solo recientemente. Como podemos ver en la captura de pantalla anterior, Node
hereda EventTarget
, por lo que todos los nodos DOM tienen método addEventListener
.
Aqui esta el truco
Sugiero un extremadamente ligero Node
escriba para actuar como nuestro bus de escucha de eventos: un comentario HTML (<!--
comment
-->
).
Para un motor de renderizado de navegador, los comentarios HTML son solo notas en el código que no tienen más funcionalidad que el texto descriptivo para los desarrolladores. Pero como los comentarios todavía están escritos en HTML, terminan en el DOM como nodos reales y tienen su propia interfaz prototipo:Comment
—Que hereda Node
.
El Comment
la clase se puede crear a partir de new
directamente como EventTarget
poder:
const myEventBus = new Comment('my-event-bus');
También podríamos usar el antiguo, pero ampliamente respaldado document.createComment
API. Requiere un data
parámetro, que es el contenido del comentario. Incluso puede ser una cadena vacía:
const myEventBus = document.createComment('my-event-bus');
Ahora podemos emitir eventos usando dispatchEvent
, que acepta un Event
Objeto. Para pasar datos de eventos definidos por el usuario, utilice CustomEvent
, donde el detail
El campo se puede utilizar para contener cualquier dato.
myEventBus.dispatchEvent(
new CustomEvent('event-name', {
detail: 'event-data'
})
);
Compatible con Internet Explorer 9-11 CustomEvent
, pero ninguna de las versiones admite new CustomEvent
. Es complejo simularlo usando document.createEvent
, por lo que si el soporte de IE es importante para usted, hay una manera de rellenarlo.
Ahora podemos vincular a los oyentes de eventos:
myEventBus.addEventListener('event-name', ({ detail }) => {
console.log(detail); // => event-data
});
Si un evento tiene la intención de activarse solo una vez, podemos usar { once: true }
para encuadernación única. Otras opciones no encajarán aquí. Para eliminar los detectores de eventos, podemos usar el nativo removeEventListener
.
Depuración
La cantidad de eventos vinculados a un bus de evento único puede ser enorme. También puede haber pérdidas de memoria si se olvida de eliminarlas. ¿Qué pasa si queremos saber cuántos eventos están vinculados a myEventBus
?
myEventBus
es un nodo DOM, por lo que DevTools puede inspeccionarlo en el navegador. Desde allí, podemos encontrar los eventos en la pestaña Elementos → Escuchas de eventos. Asegúrate de desmarcar “Ancestros” para ocultar los eventos vinculados document
y window
.
Un ejemplo
Un inconveniente es que la sintaxis de EventTarget
es un poco detallado. Podemos escribir una envoltura simple para él. Aquí hay una demostración en TypeScript a continuación:
class EventBus<DetailType = any> {
private eventTarget: EventTarget;
constructor(description = '') { this.eventTarget = document.appendChild(document.createComment(description)); }
on(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener); }
once(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener, { once: true }); }
off(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.removeEventListener(type, listener); }
emit(type: string, detail?: DetailType) { return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail })); }
}
// Usage
const myEventBus = new EventBus<string>('my-event-bus');
myEventBus.on('event-name', ({ detail }) => {
console.log(detail);
});
myEventBus.once('event-name', ({ detail }) => {
console.log(detail);
});
myEventBus.emit('event-name', 'Hello'); // => Hello Hello
myEventBus.emit('event-name', 'World'); // => World
La siguiente demostración proporciona JavaScript compilado.
¡Y ahí lo tenemos! Acabamos de crear un bus de escucha de eventos libre de dependencias donde un componente puede informar a otro componente de los cambios para desencadenar una acción. No se necesita una biblioteca completa para hacer este tipo de cosas, y las posibilidades que abre son bastante infinitas.