为强类型事件添加定义



我正在尝试弄清楚如何有效地将强类型事件添加到我的项目中,但一直遇到奇怪的问题。理想情况下,我希望能够做这样的事情:

declare class EventEmitter<T> {
on<K extends keyof T>(event: K, fn: (...args: T[K]) => void, context?: any): void;
once<K extends keyof T>(event: K, fn: (...args: T[K]) => void, context?: any): void;
emit<K extends keyof T>(event: K, ...args: T[K]): boolean;
}
interface MyEvents {
'eventA': [string, number];
'eventB': [string, { prop: string, prop2: number }, (arg: string) => void];
}
class MyEmitter extends EventEmitter<MyEvents> {
// ...
}
const myEmitter = new MyEmitter();
myEmitter.on('eventA', (str, num) => {});
myEmitter.once('eventB', (str, obj, fn) => {});
myEmitter.emit('eventA', 'foo', 3);

第一个问题是,显然元组不是 rest 参数的有效类型,尽管只是底层的类型化元素数组(我相信目前正在研究(。我想如果我放弃键入emit方法,而只是让我的事件映射到函数类型而不是元组,那就好了。这也将提供一些关于参数是什么的额外信息的好处。

declare class EventEmitter<T> {
on<K extends keyof T>(event: K, fn: T[K], context?: any): void;
once<K extends keyof T>(event: K, fn: T[K], context?: any): void;
}
interface MyEvents {
'eventA': (str: string, num: number) => void;
'eventB': (
str: string,
data: { prop: string, prop2: number }, 
fn: (arg: string) => void
) => void;
}
class MyEmitter extends EventEmitter<MyEvents> {
// ...
}
const myEmitter = new MyEmitter();
myEmitter.on('eventA', (str, num) => {});
myEmitter.once('eventB', (str, obj, fn) => {});

在这一点上,我被难住了。IntelliSense 可以推断出ononce的正确签名,但实际的参数类型仅针对回调中参数最多的事件推断,这对我来说毫无意义。几天前我打开了一个问题,但尚未得到回复。我不确定这是否真的是一个错误,或者我是否忽略了某些东西。

与此同时,是否有任何有效的方法可以做到这一点?我已经考虑过像这样向我的发射器类添加重载(这里EventEmitter只是使用节点类型(:

class MyEmitter extends EventEmitter {
on(event: 'eventA', fn: (str: string, num: number) => void);
on(event: 'eventB', fn: (
str: string,
data: { prop: string, prop2: number },
fn: (arg: string) => void
) => void);
}

但是,这要求我在类中实际实现on,如果我想要onceemit的类型,则必须复制所有事件定义。有没有更好的解决方案?

我评论了您报告的问题; 它看起来确实有问题。 显然,您可以通过在函数参数上注释类型来解决此问题。 这很烦人,但它有效:

myEmitter.on('eventA', (str: string, num: number) =>{}); // no errors
myEmitter.on('eventA', (str: string) =>{}); // no error, [see FAQ](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters)
myEmitter.on('eventA', (str: number) =>{}); // error as expected
myEmitter.on('eventA', (str: string, num: number, boo: boolean) =>{}); // error as expected

说得对,您不能将元组类型用作 rest 参数。 您可以通过将元组转换为数组来解决此问题,但现在它忘记了顺序:

type AsArray<T extends any[]> = (T[number])[]
declare class EventEmitter<T extends {[K in keyof T]: any[]}> {
on<K extends keyof T>(event: K, fn: (...args: AsArray<T[K]>) => void, context?: any): void;
once<K extends keyof T>(event: K, fn: (...args: AsArray<T[K]>) => void, context?: any): void;
emit<K extends keyof T>(event: K, ...args: AsArray<T[K]>): boolean;
}
myEmitter.emit('eventA', 2, 1); // oops, rest args are string|number

您可以通过计算出合理的函数参数的最大数量(例如 4 个(并以这种方式声明它们来更接近:

type TupleFunctionParams<T extends any[], R=void> = {
(a0: T[0], a1: T[1], a2: T[2], a3: T[3], a4: T[4], ...a5Plus: AsArray<T>): R
}
declare class EventEmitter<T extends {[K in keyof T]: any[]}> {
on<K extends keyof T>(event: K, fn: TupleFunctionParams<T[K]>, context?: any): void;
once<K extends keyof T>(event: K, fn: TupleFunctionParams<T[K]>, context?: any): void;
emit<K extends keyof T>(event: K, a0?: T[K][0], a1?: T[K][1], a2?: T[K][2], a3?: T[K][3], a4?: T[K][4], ...args: AsArray<T[K]>): boolean;
}

您在元组中指定的参数现在将按正确的顺序显示。 但是,您仍然可以省略参数(请参阅常见问题解答(,并且仍然可以指定额外的参数,这些参数将是元组中类型的联合:

myEmitter.emit('eventA', 1, 2); // error as expected
myEmitter.emit('eventA', 'one', 2); // works
myEmitter.emit('eventA', 'one'); // also works, all args are optional
myEmitter.emit('eventA', 'one', 2, 3) // ALSO works, because subsequent args are union
myEmitter.on('eventA', (str, num) => { }); // works
myEmitter.on('eventA', (str) => { }); // as above
myEmitter.on('eventA', (str, num, rando) => { }); // as above, rando is string | number

在那之后,我能做的最好的事情就是将一堆never附加到你的元组中:

interface MyEvents {
'eventA': [string, number, never, never, never, never, never];
'eventB': [string, { prop: string, prop2: number }, (arg: string) => void, never, never, never, never];
}

现在至少额外的参数会倾向于被键入一些有用的:

myEmitter.emit('eventA', 'one', 2, 3) // Error on 3, should be undefined
myEmitter.on('eventA', (str, num, rando) => { }); // now rando is never

在操场上看

好的,这是我能做的最好的事情。 希望对您有所帮助。 祝你好运!

最新更新