添加强类型事件的定义

时间:2017-07-28 04:48:42

标签: typescript events

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

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);

第一个问题是,显然元组不是有效类型的休息参数,尽管只是引擎盖下的类型元素数组(我相信目前正在处理)。我想,如果我放弃键入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的正确签名,但实际的参数类型仅针对其回调中具有最多参数的事件进行推断,这使得 no 感觉到我。我几天前开了an issue,但尚未得到答复。我不确定这实际上是一个错误,还是我忽略了什么。

与此同时,有没有有效的方法呢?我已经考虑过像这样向我的发射器类添加重载(这里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的类型,我必须复制所有的事件定义。有更好的解决方案吗?

1 个答案:

答案 0 :(得分:1)

commented报告您的问题;它似乎有点儿。显然,你可以通过在函数参数上注释类型来解决它。这很烦人,但它确实有效:

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

你是对的,你不能使用元组类型作为休息参数。您可以通过将元组转换为数组来类型解决这个问题,但现在它会忘记订单:

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;
}

您在元组中指定的参数现在将以正确的顺序显示。但是,你仍然可以省略参数(see FAQ),你仍然可以指定额外的参数,这些参数将是元组中类型的联合:

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

See it on the playground

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