microsoft / microsoft/TypeScript

Add a generic parameter to EventTarget for mapping events

Offen
#43,477 4 Kommentare 33 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Awaiting More Feedback Suggestion
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

Suggestion

🔍 Search Terms

EventTarget, addEventListener, removeEventListener

✅ Viability Checklist

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Currently we can extend EventTarget to hook any given class into the DOM events system:

class Foo extends EventTarget {}

const instance = new Foo();
instance.addEventListener('my-event', (ev) => { ... });

However, in the case of EventTarget, it has an addEventListener definition like so:

interface EventTarget {
  addEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject | null,
    options?: boolean | AddEventListenerOptions
  ): void;

  dispatchEvent(event: Event): boolean;

  removeEventListener(
    type: string,
    callback: EventListenerOrEventListenerObject | null,
    options?: EventListenerOptions | boolean
  ): void;
}

declare var EventTarget: {
    prototype: EventTarget;
    new(): EventTarget;
};

Due to this, it seems impossible to strongly type the events of our class.

Remember how things like window work:

addEventListener<K extends keyof WindowEventMap>(
  type: K,
  listener: (this: Window, ev: WindowEventMap[K]) => any,
  options?: boolean | AddEventListenerOptions
): void;

This means we have strongly typed event names and types, giving us good intellisense when we do window.addEventListener.

So my suggestion is that we do similar for EventTarget:

interface EventTarget<EventMap> {
  addEventListener<K extends keyof EventMap>(
    type: K,
    listener: (this: any, ev: EventMap[K]) => any, // `this` has to be `any` i suppose since we don't know what it is at this point
    options?: boolean | AddEventListenerOptions
  ): void;
}

We'd still want an overload which consumes string but that would just fall back to Event.

Also, I'm unaware of any work around, so if there is already a known one, please do tell.

We also can't override this as a workaround, for example:

class Foo extends EventTarget {
  addEventListener<K extends keyof FooMap>(
    type: K,
    listener: (ev: FooMap[K]) => void,
    options?: boolean | AddEventListenerOptions
  ): void;
  // ...[implementation and other overloads here]
}

as this would be like doing:

type Handler = (ev: Event) => void;

const myHandler: Handler = (ev: CustomEvent) => void; // error since it needs to handle ANY `Event`

You could possibly hack around it with interfaces:

interface FooEventTarget extends EventTarget {
  addEventListener<K extends keyof FooMap>(
    type: K,
    listener: (ev: FooMap[K]) => void,
    options?: boolean | AddEventListenerOptions
  ): void;
  addEventListener(
    type: string,
    callback: EventListenerOrEventListenerObject | null,
    options?: EventListenerOptions | boolean
  ): void;
}

const eventTarget = EventTarget as {new(): FooEventTarget; prototype: FooEventTarget};

class MyClass extends eventTarget {}

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit den im Issue gezeigten Deklarationen von EventTarget, addEventListener, removeEventListener und dispatchEvent und vergleiche sie anschließend mit der vorhandenen WindowEventMap-Überladung. Definiere, was die Unterstützung generischer Event-Maps und der Fallback von string zu Event beibehalten sollen; als abgeschlossen gilt die Aufgabe, wenn benutzerdefinierte Event-Namen und Payloads stark typisiert sind, ohne das Laufzeitverhalten von JavaScript zu ändern oder bestehende Aufrufe zu beeinträchtigen.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, typescript
Bereich
frontend, web-dev
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.