How to write generic functions for EventEmitter or EventTarget?

Viewed 53

I want to write a generic addListener function that works with any EventTarget. I'd like to infer the callback function's parameters using the event emitter's types and the event name, similar to how addEventListener works.

Essentially, I want this code to work without using any:

addListener(element, 'click', (event) => {
  // event is automatically inferred to be `MouseEvent`
  ...
});

My use case is that I want to polyfill event target options like once and signal onto Node EventEmitters, which have a similar shape to EventTarget. I'm asking the question about EventTarget since it has built-in examples and our EventEmitter types are custom.

1 Answers

You can see the implementation for addEventListener

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

You'd do something similar:

type ValidHandlers = {
    "click": "MouseEvent"
}
const addListener = <T extends keyof ValidHandlers>(element:any, type: T, callback: (event: ValidHandlers[T]) => void) => {

}
addListener({}, 'click', (event) => {
  // event is automatically inferred to be `MouseEvent`

});

If you want callback signatures to differ, just do the same thing but define entire function signatures.

type CallbackSignatures = {
    "click": (event: "MouseEvent", foobar: 123) => void,
    "foob": (baz: 'baz') => void
}
const addListener = <T extends keyof CallbackSignatures>(element:any, type: T, callback: CallbackSignatures[T]) => {

}
addListener({}, 'click', (event, foobar) => {

  // event is automatically inferred to be `MouseEvent`

});
Related