简体   繁体   中英

How to narrow callback event type based on event name

I'm trying to implement an event emitter. The code is very simple.

Right now tsc thinks that the event type in eventHandler is 'ErrorEvent' | 'MessageEvent' 'ErrorEvent' | 'MessageEvent' . This is the problem and I want tsc to narrow the types to the correct one (depending on eventName passed to the on function).

Can it be done?

Definitions

interface Event {
  code: string;
}

interface ErrorEvent extends Event {
  xxx: number;
}

interface MessageEvent extends Event {
  yyy: number;
}

interface Events {
  error: ErrorEvent,
  message: MessageEvent
}

type EventHandler = (event: Events[keyof Events]) => void;

TS CODE

function on (eventName: keyof Events, eventHandler: EventHandler) {
  console.log(eventName)
}

on('message', (event) => { // ErrorEvent | MessageEvent
  console.log(event)
  console.log(event.code)
  console.log(event.xxx) //  Property 'xxx' does not exist on type 'ErrorEvent | MessageEvent'.
  console.log(event.yyy) //  Property 'yyy' does not exist on type 'ErrorEvent | MessageEvent'.
})

First you have to get rid of EventHandler as a union type:

type EventHandler<K extends EventKeys> = (event: Events[K]) => void

That way you get a concrete type for you handler for each concrete K . And then you make sure second argument type of the on function depends on the first one. Thus you have to introduce a type parameter to you function to get types in line:

function on <K extends EventKeys>(eventName: K, eventHandler: EventHandler<K>): void {}

I believe this should do:

interface Event {
  code: string;
}

interface ErrorEvent extends Event {
  xxx: number;
}

interface MessageEvent extends Event {
  yyy: number;
}

interface Events {
  error: ErrorEvent,
  message: MessageEvent
}

type EventKeys = keyof Events
type EventHandler<K extends EventKeys> = (event: Events[K]) => void;

function on <K extends EventKeys>(eventName: K, eventHandler: EventHandler<K>): void {
  console.log(eventName)
}

on('message', (event) => {
  console.log(event)
  console.log(event.code)
  console.log(event.xxx) //  Property 'xxx' does not exist on type 'MessageEvent<any>'.
  console.log(event.yyy)
})

TS playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM