2017-07-28 97 views
2

我想弄清楚如何有效地將強類型事件添加到我的項目中,但仍然遇到奇怪的問題。理想情況下,我想能夠做這樣的事情:爲強類型事件添加定義

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) => {}); 

在這一點上,我很難過。智能感知可以推斷出ononce的正確簽名,但實際參數類型僅針對其回調參數最多的事件推斷,這使得沒有對我有意義。前幾天我打開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

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

好吧,這是我能做到的最好。希望能幫助到你。祝你好運!