添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
帅气的弓箭  ·  linevent_book.md·  5 天前    · 
狂野的伤疤  ·  webContents | Electron·  1 周前    · 
喝醉的烤面包  ·  webContents | FAQ·  1 周前    · 
成熟的遥控器  ·  Learn @click event ...·  1 周前    · 
英俊的茴香  ·  Webflux + Jwt(es256) ...·  1 周前    · 
严肃的柚子  ·  BONLUCK set @ 44° - ...·  2 周前    · 
仗义的山羊  ·  modbus ...·  3 月前    · 

Recently I have been working on improving a whole bunch of web components in one of our code bases. Part of this has been to improve the types and their strength (which also means better editor support :D).

These are just a few tips from what I found.

The aim

It is common that we might want to emit custom events from our components. For example:

class MyElement extends HTMLElement {
  // ..
  protected _doSomething(): void {
    this.dispatchEvent(new CustomEvent('my-event'));

In these situations, a consumer of the component would listen like so:

node.addEventListener('my-event', (ev) => {
  // ...

However, there would be no helpful type information to suggest that my-event is a valid (“known”) event and the type of the event it is.

So our aim to strengthen this would be to strongly type those events and give editors better completion/information.

Event maps

Thankfully, TypeScript already helped solve this problem with “event maps”. These are basically interfaces to hold the names of events and their type.

You can see that addEventListener is roughly typed like so:

// example map
interface EventMap {
  'my-event': MyCustomEventType;
// method
addEventListener<T extends keyof EventMap>(
  type: T,
  listener: (event: EventMap[T]) => any

Where EventMap is one of a few available maps depending on the type of node you’re dealing with.

The fall back, for when it isn’t in the event map, is basically Event. This means we’re not too strict.

Anyhow this results in useful hints and types like so:

document.addEventListener('load', fn); // knows that 'load' is an event
node.addEventListener('blur', (ev) => { ... }); // knows `ev` is `FocusEvent`
node.addEventListener('doesntexist', (ev) => { ... }); // `ev` is `Event`

Built in event maps

There are several, a couple are:

  • DocumentEventMap - defines all events available to Document
  • WindowEventMap - defines all events available to Window
  • HTMLElementEventMap - defines all events available to any HTML element
  • Extending a built in map

    You can extend a built in event map by augmenting the global interface:

    declare global {
      interface WindowEventMap {
        'my-event': CustomEvent<{foo: number}>;
    // ...
    window.addEventListener(
      'my-event', // will be a known event name, suggested in your editor
      (ev) => { // will be typed as `CustomEvent<{foo: number}>`
        ev.detail.foo; // number
    

    Defining your own event maps

    You may want to tie events to your component and only your component, rather than the globally available list of events.

    To do this, you can re-define addEventListener:

    interface MyEventMap {
      'my-event': CustomEvent<{foo: number}>;
    class MyElement extends HTMLElement {
      public addEventListener<T extends keyof MyEventMap>(
        // the event name, a key of MyEventMap
        type: T,
        // the listener, using a value of MyEventMap
        listener: (this: MyElement, ev: MyEventMap[T]) => any,
        // any options
        options?: boolean | AddEventListenerOptions
      ): void;
      // the fallback for any event names not in our map
      public addEventListener(
        type: string,
        listener: (this: MyElement, ev: Event) => any,
        options?: boolean | AddEventListenerOptions
      ): void {
        super.addEventListener(type, listener, options);
    // ...
    const node = document.createElement('my-element');
    node.addEventListener(
      'my-event', // strongly typed, suggested by editor
      (ev) => { ... } // ev is a `CustomEvent<{foo: number}>`
    

    It looks like there’s a lot going on here, but really we are just copying what the original definition of addEventListener is from TypeScript’s DOM definitions.

    Wrap up

    Again, this was just a quick one to show the findings. These things can make the dev experience super nice, though.

    Now in our editors we can get strongly typed event names along with their strongly typed events, rather than relying on casts and what not.