Back

Ein Entwicklerhandbuch zu JavaScript Custom Events

Ein Entwicklerhandbuch zu JavaScript Custom Events

Sie haben eine saubere Komponentenarchitektur aufgebaut, aber jetzt müssen die einzelnen Teile miteinander kommunizieren, ohne eine enge Kopplung zu erzeugen. Native DOM-Events behandeln Benutzerinteraktionen gut, aber was ist mit Ihren eigenen anwendungsspezifischen Signalen? JavaScript Custom Events lösen dieses Problem elegant.

Dieser Leitfaden behandelt das Erstellen und Dispatchen von Custom Events, das Übergeben strukturierter Daten über die CustomEvent-Detail-Payload und die Verwendung von EventTarget als leichtgewichtigen Event-Bus. Sie lernen außerdem, wie Custom Events in Web Components funktionieren und wie Custom Events im Shadow DOM über Grenzen hinweg propagieren.

Wichtigste Erkenntnisse

  • Verwenden Sie den CustomEvent-Konstruktor mit der detail-Eigenschaft, um strukturierte Daten durch Events zu übergeben
  • Das EventTarget-Interface funktioniert eigenständig und ermöglicht leichtgewichtige Pub/Sub-Muster ohne DOM-Elemente
  • Setzen Sie bubbles: true für Events, die im DOM-Baum nach oben propagieren sollen
  • Verwenden Sie composed: true, um Events das Überschreiten von Shadow-DOM-Grenzen in Web Components zu ermöglichen

Erstellen und Dispatchen von Custom Events

Der CustomEvent-Konstruktor ist die moderne Methode zum Erstellen von Custom Events. Vergessen Sie initCustomEvent – das ist eine Legacy-API, die Sie in modernen Browsern nicht benötigen werden.

const event = new CustomEvent('user-login', {
  detail: { userId: 123, timestamp: Date.now() },
  bubbles: true,
  cancelable: true
})

document.querySelector('#app').dispatchEvent(event)

Drei Optionen sind hier wichtig:

  • detail: Ihre strukturierte Payload (beliebige serialisierbare Daten)
  • bubbles: Ob das Event im DOM-Baum nach oben propagiert
  • cancelable: Ob Listener preventDefault() aufrufen können

Das Lauschen funktioniert genau wie bei nativen Events:

document.querySelector('#app').addEventListener('user-login', (e) => {
  console.log(e.detail.userId) // 123
})

Die CustomEvent-Detail-Payload

Die detail-Eigenschaft ist der Bereich, in dem Custom Events gegenüber dem grundlegenden Event-Konstruktor glänzen. Während Sie technisch gesehen beliebige Eigenschaften nach der Erstellung einem Event-Objekt zuweisen könnten, bietet detail einen dedizierten, konfliktfreien Namensraum für Ihre Daten.

const cartEvent = new CustomEvent('cart-updated', {
  detail: {
    items: [{ id: 1, qty: 2 }, { id: 3, qty: 1 }],
    total: 59.99,
    currency: 'USD'
  }
})

Handler greifen über event.detail darauf zu – sauber und vorhersehbar.

DOM EventTarget als Event-Bus

Sie benötigen kein DOM-Element, um Events zu verwenden. Das EventTarget-Interface funktioniert eigenständig und eignet sich daher perfekt für einen leichtgewichtigen Pub/Sub-Mechanismus:

class AppEventBus extends EventTarget {
  emit(eventName, data) {
    this.dispatchEvent(new CustomEvent(eventName, { detail: data }))
  }
  
  on(eventName, handler) {
    this.addEventListener(eventName, handler)
  }
  
  off(eventName, handler) {
    this.removeEventListener(eventName, handler)
  }
}

const bus = new AppEventBus()
bus.on('notification', (e) => console.log(e.detail.message))
bus.emit('notification', { message: 'Hallo!' })

Dieses auf EventTarget basierende Event-Bus-Muster hält Komponenten entkoppelt, ohne externe Abhängigkeiten. Für TypeScript-Benutzer können Sie die Detail-Payload mit Generics typisieren: CustomEvent<{ message: string }>.

Hinweis: In Node.js existiert EventTarget, aber CustomEvent ist erst ab Node 19+ als Global verfügbar. Node’s EventEmitter bleibt dort das gängigere Muster.

Custom Events in Web Components

Web Components verlassen sich stark auf Custom Events für die Kommunikation nach außen. Eine Komponente dispatcht Events, während der übergeordnete Code lauscht:

class UserCard extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('user-selected', {
        detail: { id: this.dataset.userId },
        bubbles: true,
        composed: true
      }))
    })
  }
}
customElements.define('user-card', UserCard)

Das übergeordnete Element lauscht, ohne die Interna der Komponente zu kennen:

document.querySelector('user-card').addEventListener('user-selected', (e) => {
  loadUserProfile(e.detail.id)
})

Shadow-DOM-Custom-Events und die Composed-Option

Shadow-DOM-Custom-Events verhalten sich unterschiedlich, abhängig von der composed-Option:

  • composed: false (Standard): Das Event stoppt an der Shadow-Root-Grenze. Die interne Implementierung bleibt verborgen.
  • composed: true: Das Event überschreitet Shadow-Grenzen und bubbelt durch das Light-DOM.
// Innerhalb des Shadow DOM
this.shadowRoot.querySelector('button').dispatchEvent(
  new CustomEvent('internal-action', {
    bubbles: true,
    composed: true // Verlässt die Shadow-Grenze
  })
)

Wenn ein Event die Shadow-Grenze überschreitet, wird event.target auf das Host-Element umgeleitet – Listener außerhalb sehen die Komponente, nicht ihre interne Struktur.

Verwenden Sie composed: true für Events, die externer Code behandeln soll. Behalten Sie composed: false für die interne Komponentenkommunikation bei.

Ein kurzer Hinweis zu event.isTrusted: Diese Eigenschaft gibt an, ob der Browser (Benutzeraktion) oder ein Skript das Event erzeugt hat. Sie ist informativ, kein Sicherheitsmechanismus – verlassen Sie sich nicht darauf für die Zugriffskontrolle.

Fazit

JavaScript Custom Events bieten eine Framework-agnostische Möglichkeit, lose gekoppelte, ereignisgesteuerte Architekturen aufzubauen. Verwenden Sie CustomEvent mit einer strukturierten Detail-Payload für Daten, nutzen Sie EventTarget als eigenständigen Event-Bus und verstehen Sie, wie bubbles und composed die Propagierung steuern – insbesondere über Shadow-DOM-Grenzen hinweg. Diese Muster skalieren von einfacher Komponentenkommunikation bis hin zu komplexen Micro-Frontend-Architekturen.

Häufig gestellte Fragen

Der Hauptunterschied ist die detail-Eigenschaft. CustomEvent enthält eine dedizierte detail-Eigenschaft zum Übergeben strukturierter Daten, während der grundlegende Event-Konstruktor dies nicht tut. Obwohl Sie nach der Erstellung Eigenschaften zu einem Event-Objekt hinzufügen können, ist die Verwendung von CustomEvent mit detail sauberer und vermeidet potenzielle Namenskonflikte mit vorhandenen Event-Eigenschaften.

Ja, aber mit Einschränkungen. React unterstützt Custom-Event-Listener nicht nativ in JSX, daher müssen Sie Refs verwenden und Listener manuell mit addEventListener anhängen. Vue behandelt Custom Events besser durch sein Event-System. Custom Events funktionieren am besten bei der Kommunikation zwischen Framework-Komponenten und Vanilla-Web-Components oder Framework-agnostischem Code.

Nein. Setzen Sie bubbles nur dann auf true, wenn übergeordnete Elemente das Event abfangen müssen. Für direkte Kommunikation, bei der Sie auf demselben Element dispatchen und lauschen, ist Bubbling unnötig. Übermäßiges Bubbling kann dazu führen, dass unbeabsichtigte Listener Events abfangen, seien Sie also bewusst bei der Propagierung.

Verwenden Sie removeEventListener mit genau derselben Funktionsreferenz, die Sie an addEventListener übergeben haben. Anonyme Funktionen können nicht entfernt werden, da es keine Referenz zum Übergeben gibt. Speichern Sie Ihre Handler-Funktion in einer Variablen oder verwenden Sie das AbortController-Muster mit der signal-Option für eine sauberere Bereinigung.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay