在 Typescript 中使用泛型强制执行相同的参数列表

Enforcing same argument list using generic in Typescript

我正在尝试在 EventEmitter 上创建一个 typescript enforcer。我的目标是创建一个带有 addListenerremoveListeneremit 方法的发射器,如果我传递一个无效事件或者即使我传递一个不匹配的函数,它们也会在编译时中断带有事件签名(args 和 return)。

为了使这成为可能,我需要一个 Map 接口(一些带有函数列表的接口)一个例子可以在类型 native GlobalEventHandlersEventMap 中看到:

lib.dom.d.ts

//...
interface GlobalEventHandlersEventMap {
  "abort": UIEvent;
  "animationcancel": AnimationEvent;
  "animationend": AnimationEvent;
  "animationiteration": AnimationEvent;
  //...

到目前为止,我正确地掌握了前两种方法:

import { EventEmitter } from "events";

// A simple mapping interface
interface SomeMap {
  greeting: (message: string) => void;
}

// This generalization of function is necessary to track
// the list of arguments and result types.
interface GFunc<TArgs extends any[] = any[], TResult = any> {
  (...args: TArgs): TResult;
}

// This is a advanced type to mask the EventEmitter class
interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  addListener<K extends keyof T>(event: K, listener: T[K]): void;
  removeListener<K extends keyof T>(event: K, listener: T[K]): void;
}

const emitter: AdvEventEmitter<SomeMap> = new EventEmitter();

emitter.addListener('greeting', msg => {
  console.log(msg);
});

在上面的代码中,AdvEventEmitter接口能够对第一个参数强制约束:

emitter.addListener('something_else', () => {});

Msg: Argument of type '"something_else"' is not assignable to parameter of type '"greeting"'.

甚至在第二个参数中强制执行参数的类型和数量:

emitter.addListener('greeting', (m1, m2) => {
  console.log(m1, m2);
});

Msg: Argument of type '(m1: any, m2: any) => void' is not assignable to parameter of
type '(message: string) => void'.

太棒了。

现在问题是 emit 方法。

我正在尝试这样的事情:

interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  // ...
  emit<K extends keyof T, TArgs extends any[] = any[]>(event: K, ...args: TArgs): void;
}

arg event 已正确验证(如预期),但 args 只是任意列表的通用列表。我不知道如何将 TArgs 约束与 K 约束联系起来。

有没有办法强制执行此限制?

您需要从T中提取K函数的参数。您可以使用预定义的 Parameters 条件类型执行此操作:


interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void;
}

Playground Link