microsoft / microsoft/TypeScript

Add type to retrieve valid events of an EventTarget

Aperta
#33,047 6 commenti 21 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Awaiting More Feedback Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.4k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

Search Terms

EventListener, EventMap, EventTarget

Suggestion

There should a type to retrieve valid event names and listeners for a given EventTarget. Window has the following method:

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

Every EventTarget should have its own corresponding EventMap (even if no special event types are available for a given target, thus, resulting in an empty object). The EventMap of a given EventTarget could be retrieved as follows:

type WindowEventMap = EventMap<Window>;
type DocumentEventMap = EventMap<Document>;
type AudioNodeEventMap = EventMap<AudioNode>; // Empty interface
// ...

Use Cases

I tried creating a function which encapsulates an event for easier lifecycle management with React Hooks:

export function managedEventListener<T extends EventTarget, K extends string>(
  target: T,
  type: K,
  callback: EventListener,
  options?: AddEventListenerOptions,
) {
  target.addEventListener(type, callback, options);
  return () => {
    target.removeEventListener(type, callback, options);
  };
}

Unfortunately, EventListener gives no proper IntelliSense and I had to use the as EventListener syntax like below, as suggested in #28357:

useEffect(
  () =>
    managedEventListener(window, 'deviceorientation', ((
      event: DeviceOrientationEvent,
    ) => {
      setOrientation(event);
    }) as EventListener),
  [],
);

My goal was to simplify the syntax to the following, with proper type inference:

useEffect(
  () =>
    managedEventListener(window, 'deviceorientation', event => {
      setOrientation(event);
    }),
  [],
);

Using conditional types, I was able to achieve the syntax above by replacing EventListener with a specialized EventListenerCallback type which extracts values from the 2 most commonly used event maps, namely WindowEventMap and DocumentEventMap:

type ExtractFrom<T, K> = K extends keyof T ? T[K] : never;
export type EventListenerCallback<T, K> = T extends Window
  ? (event: ExtractFrom<WindowEventMap, K>) => any
  : (T extends Document
      ? (event: ExtractFrom<DocumentEventMap, K>) => any
      : EventListener);

The new code for managedEventListener was born:

export function managedEventListener<T extends EventTarget, K extends string>(
  target: T,
  type: K,
  callback: EventListenerCallback<T, K>,
  options?: AddEventListenerOptions,
) {
  target.addEventListener(type, callback, options);
  return () => {
    target.removeEventListener(type, callback, options);
  };
}

Examples

The code above could be greatly simplified by introducing the aforementioned EventMap<EventTarget> type:

export function managedEventListener<T extends EventTarget, K extends keyof EventMap<T>>(
  target: T,
  type: K,
  callback: (this: T, ev: EventMap<T>[K]) => any,
  options?: AddEventListenerOptions,
) {
  target.addEventListener(type, callback, options);
  return () => {
    target.removeEventListener(type, callback, options);
  };
}

Checklist

My suggestion meets these guidelines:

  • 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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia esaminando i tipi esistenti degli event listener di EventTarget, inclusi WindowEventMap e DocumentEventMap, e confrontali con gli esempi proposti di EventMap. Determina se ogni EventTarget può esporre una event map corrispondente e come debbano essere dedotti i nomi generici degli eventi e i parametri del listener. Il lavoro è completato quando il tipo richiesto supporta l’utilizzo mostrato di managedEventListener senza cast.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.