如何强类型化事件发射器,以便从事件类型推断处理程序的参数类型?



我正在尝试定义一个强类型事件发射器,我最想要的是从传递给addEventHandler函数的字符串中推断回调的事件类型。

但到目前为止,我已经失败了,我所想到的是从回调推断事件类型,而不是相反。

这里有一个例子(用小提琴(:

interface NumberEvent {
  type: 'NumberEvent';
  num: number;
}
interface StringEvent {
  type: 'StringEvent';
  str: string;
}
type AnyEvent = NumberEvent | StringEvent;
const addEventHandler = <ET extends AnyEvent>(type: ET['type'], handler: ((event: ET) => void)) => {
  console.log(`added event handler for ${type}`);
}
addEventHandler('NumberEvent', (event: NumberEvent) => {
  // this is cool
});
addEventHandler('NumberEvent', (event: StringEvent) => {
  // this doesn't type check, good
});
addEventHandler('type does not exist', (x: any) => {
  // why no type error?
});

我不明白为什么最后一行类型检查,因为没有类型为'type does not exist'AnyEvent实例。

你能想出一个更好的方法来解决这个问题吗?

您可以通过使addEventHandler在事件类型而不是事件对象上通用来实现这一点。

const addEventHandler = <ET extends AnyEvent['type']>(
  type: ET,
  handler: ((event: Extract<AnyEvent, { type: ET }>) => void)
) => {
  console.log(`added event handler for ${type}`);
}

您也可以使用AnyEvent & { type: ET }而不是Extract<AnyEvent & { type: ET }

您的类型不能阻止最后一种情况的原因是ET被推断为anyany["type"]仍然是any,因此它将允许任何字符串。

上面的版本仍然不会阻止某人这样做:

addEventHandler<any>('type does not exist', (x: any) => {
  // explicitly providing <any>
});

通过使用<anything> & any就是any这一事实,你可以防止这种情况发生,但就我个人而言,我不会打扰。除非有意破坏您的类型,否则没有人可能在此处提供any。使用any检查,您也可以返回到您的通用:

type NotAny<T> = 0 extends (1 & T) ? never : T; 
const addEventHandler = <ET extends AnyEvent>(type: NotAny<ET["type"]>, handler: ((event: ET) => void)) => {
  console.log(`added event handler for ${type}`);
}

最新更新