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

How to strongly type an event emitter such that the handler's parameter type is inferred from the event type?

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

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

这是一个例子(带有 fiddle):

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> & anyany 的事实来避免这种情况,但我个人不会打扰。除非故意破坏您的类型,否则没有人会在此处提供 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}`);
}