Erstellen wir einen leichtgewichtigen nativen Event-Bus in JavaScript

Avatar of Carter Li
Carter Li am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ein Event-Bus ist ein Entwurfsmuster (und obwohl wir hier über JavaScript sprechen, ist es ein Entwurfsmuster in jeder Sprache), das zur Vereinfachung der Kommunikation zwischen verschiedenen Komponenten verwendet werden kann. Es kann auch als Publish/Subscribe oder Pubsub betrachtet werden.

Die Idee ist, dass Komponenten auf den Event-Bus lauschen können, um zu wissen, wann sie ihre Aufgaben erledigen sollen. Zum Beispiel könnte eine "Tabellen"-Komponente auf Ereignisse lauschen, die ihr sagen, dass sie den aktiven Tab wechseln soll. Sicher, das könnte durch einen Klick auf einen der Tabs geschehen und somit vollständig innerhalb dieser Komponente behandelt werden. Aber mit einem Event-Bus könnten einige *andere* Elemente dem Tab mitteilen, zu wechseln. Stellen Sie sich die Übermittlung eines Formulars vor, das einen Fehler verursacht, auf den der Benutzer in einem bestimmten Tab aufmerksam gemacht werden muss, sodass das Formular eine Nachricht an den Event-Bus sendet, die der Tabs-Komponente mitteilt, den aktiven Tab zu dem mit dem Fehler zu wechseln. Das ist, wie es an Bord eines Event-Busses aussieht.

Pseudo-Code für diese Situation wäre etwa so…

// 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);  

Brauchen Sie eine JavaScript-Bibliothek dafür? (Fangfrage: Sie *brauchen nie* eine JavaScript-Bibliothek). Nun, es gibt viele Optionen da draußen

Schauen Sie sich auch Mitt an, eine Bibliothek, die nur 200 Bytes komprimiert ist. Es hat etwas an diesem einfachen Muster, das Menschen dazu inspiriert, es selbst auf die prägnanteste Weise anzugehen.

Lassen Sie uns das selbst tun! Wir werden überhaupt keine Drittanbieterbibliothek verwenden und ein ereignislauschendes System nutzen, das bereits in JavaScript integriert ist, mit dem addEventListener, das wir alle kennen und lieben.

Zuerst ein wenig Kontext

Die addEventListener API in JavaScript ist eine Member-Funktion der EventTarget Klasse. Der Grund, warum wir ein click-Ereignis an einen Button binden können, ist, dass die Prototyp-Schnittstelle von <button> (HTMLButtonElement) indirekt von EventTarget erbt.

Quelle: MDN Web Docs

Im Gegensatz zu den meisten anderen DOM-Schnittstellen kann EventTarget *direkt* mit dem Schlüsselwort new erstellt werden. Es wird in allen modernen Browsern unterstützt, aber erst ziemlich kürzlich. Wie wir im obigen Screenshot sehen können, erbt Node von EventTarget, somit haben alle DOM-Knoten die Methode addEventListener.

Hier ist der Trick

Ich schlage einen extrem leichten Node-Typ vor, der als unser ereignislauschender Bus dient: ein HTML-Kommentar (<!-- Kommentar -->.

Für eine Browser-Rendering-Engine sind HTML-Kommentare nur Notizen im Code, die keine Funktionalität haben, abgesehen von beschreibendem Text für Entwickler. Aber da Kommentare immer noch in HTML geschrieben sind, landen sie im DOM als echte Knoten und haben ihre eigene Prototyp-Schnittstelle – Comment – die von Node erbt.

Die Comment-Klasse kann mit new direkt erstellt werden, so wie EventTarget auch

const myEventBus = new Comment('my-event-bus');

Wir könnten auch die alte, aber weit verbreitete API document.createComment verwenden. Sie erfordert einen data-Parameter, der den Inhalt des Kommentars darstellt. Er kann sogar ein leerer String sein.

const myEventBus = document.createComment('my-event-bus');

Jetzt können wir Ereignisse mit dispatchEvent auslösen, was ein Event-Objekt akzeptiert. Um benutzerdefinierte Ereignisdaten zu übergeben, verwenden Sie CustomEvent, wobei das detail-Feld verwendet werden kann, um beliebige Daten zu enthalten.

myEventBus.dispatchEvent(
  new CustomEvent('event-name', { 
    detail: 'event-data'
  })
);

Internet Explorer 9-11 unterstützt CustomEvent, aber keine der Versionen unterstützt new CustomEvent. Es ist komplex, es mit document.createEvent zu simulieren. Wenn IE-Unterstützung für Sie wichtig ist, gibt es eine Möglichkeit, ein Polyfill dafür zu erstellen.

Jetzt können wir Ereignis-Listener binden

myEventBus.addEventListener('event-name', ({ detail }) => {
  console.log(detail); // => event-data
});

Wenn ein Ereignis nur einmal ausgelöst werden soll, können wir { once: true } für die einmalige Bindung verwenden. Andere Optionen passen hier nicht. Um Ereignis-Listener zu entfernen, können wir das native removeEventListener verwenden.

Debugging

Die Anzahl der an einen einzigen Event-Bus gebundenen Ereignisse kann riesig sein. Es kann auch zu Speicherlecks kommen, wenn Sie vergessen, sie zu entfernen. Was ist, wenn wir wissen wollen, *wie viele* Ereignisse an myEventBus gebunden sind?

myEventBus ist ein DOM-Knoten, sodass er von den DevTools im Browser inspiziert werden kann. Von dort aus können wir die Ereignisse im Reiter Elemente → Ereignis-Listener finden. Stellen Sie sicher, dass Sie "Vorfahren" abwählen, um Ereignisse auszublenden, die an document und window gebunden sind.

Ein Beispiel

Ein Nachteil ist, dass die Syntax von EventTarget etwas umständlich ist. Wir können einen einfachen Wrapper dafür schreiben. Hier ist ein Demo in TypeScript unten

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

Die folgende Demo liefert das kompilierte JavaScript.


Und da haben wir es! Wir haben gerade einen abhängigkeitsfreien ereignislauschenden Bus erstellt, bei dem eine Komponente eine andere Komponente über Änderungen informieren kann, um eine Aktion auszulösen. Es braucht keine vollständige Bibliothek für solche Dinge, und die Möglichkeiten, die sich dadurch eröffnen, sind schier endlos.