如何根据事件名称缩小回调事件类型

How to narrow callback event type based on event name

我正在尝试实现一个事件发射器。代码很简单

现在 tsc 认为 eventHandler 中的事件类型是 'ErrorEvent' | 'MessageEvent'。 这就是问题所在,我希望 tsc 将类型缩小为正确的类型(取决于传递给 on 函数的 eventName)。

能做到吗?

定义

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代码

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'.
})

首先你必须摆脱作为联合类型的 EventHandler:

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

这样你就可以为每个具体 K 的处理程序获得一个具体类型。 然后确保 on 函数的第二个参数类型取决于第一个。因此,您必须向函数引入一个类型参数以获取行中的类型:

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

我认为应该这样做:

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