2017-07-28 9 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) => {}); 

この時点で私は困惑しています。 IntelliSenseはonまたはonceの正しいシグネチャを推測できますが、実際の引数の型はコールバックで最も引数の多いイベントに対してのみ推測され、となります。私は数日前に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の実際の実装を持っている私を必要とし、I場合onceまたはemitのタイプが必要です。すべてのイベント定義を複製する必要があります。より良い解決策はありますか?

答えて

1

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

タプルで指定した引数が正しい順序で表示されるようになりました。私はそれの後にできる最善の束を追加することである

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 

:しかし、あなたはまだ(see FAQ)の引数を省略することができますし、まだあなたのタプルの種類の労働組合になります余分引数を指定することができます

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

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

今、少なくとも追加のパラメータはやや有効に型付けされる傾向がある:あなたのタプルへnever


See it on the playground

さて、それは私ができる最善のです。それが役に立てば幸い。がんばろう!

関連する問題