在 Typescript 中使用泛型强制执行相同的参数列表
Enforcing same argument list using generic in Typescript
我正在尝试在 EventEmitter
上创建一个 typescript enforcer。我的目标是创建一个带有 addListener
、removeListener
和 emit
方法的发射器,如果我传递一个无效事件或者即使我传递一个不匹配的函数,它们也会在编译时中断带有事件签名(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;
}
我正在尝试在 EventEmitter
上创建一个 typescript enforcer。我的目标是创建一个带有 addListener
、removeListener
和 emit
方法的发射器,如果我传递一个无效事件或者即使我传递一个不匹配的函数,它们也会在编译时中断带有事件签名(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;
}