2017-07-28 3 views
2

Ich versuche herauszufinden, wie effizient stark typisierte Ereignisse zu meinem Projekt hinzugefügt werden, aber in ungeraden Problemen weiterlaufen. Im Idealfall würde Ich mag Lage sein, so etwas zu tun:Hinzufügen von Definitionen für stark typisierte Ereignisse

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

Das erste Problem ist, dass anscheinend Tupel sind keine gültigen Typen für Ruhe Parameter trotz einfach der Haube sind Arrays von typisierten Elementen unter (ich glaube, daran wird gerade gearbeitet). Ich nehme an, das ist in Ordnung, wenn ich die emit Methode vergebe, und meine Ereignisse nur auf Funktionstypen anstelle von Tupeln abbilden. Dies würde auch den Vorteil von ein paar zusätzlichen Informationen über die Argumente geben.

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

An dieser Stelle bin ich ratlos. IntelliSense kann die richtigen Signaturen für on oder once ableiten, aber die tatsächlichen Argumenttypen werden nur für das Ereignis mit den meisten Argumenten auf seinem Rückruf zurückgeschlossen, was keine für mich Sinn macht. Ich öffnete vor ein paar Tagen an issue, habe aber noch keine Antwort bekommen. Ich bin mir nicht sicher, ob das tatsächlich ein Fehler ist oder ob ich etwas übersehen habe.

Gibt es in der Zwischenzeit irgendwelche effizienten Möglichkeiten, dies zu tun? Ich habe darüber nachgedacht, nur Überlastungen meine Emitter-Klasse wie diese Zugabe (hier EventEmitter wird nur der Knoten Typisierungen verwendet wird):

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

Doch diese mir eine tatsächliche Umsetzung der on in meiner Klasse haben erfordert, und wenn ich will Typen für once oder emit Ich muss alle meine Event-Definitionen duplizieren. Gibt es eine bessere Lösung?

Antwort

1

I commented zu Ihrem gemeldeten Problem; es scheint fehlerhaft. Natürlich könnten Sie umgehen, indem Sie die Typen auf Ihren Funktionsparametern kommentieren. Es ist ärgerlich, aber es funktioniert:

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 

Du hast Recht, dass Sie nicht Tupel-Typen als Rest Parameter verwenden können. Sie können Art von Arbeit um, dass durch das Tupel in ein Array umzuwandeln, aber jetzt vergisst es die Reihenfolge:

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 

Sie können herausfinden, eine angemessene maximale Anzahl der Funktionsparameter näher kommen (etwa 4) und Deklarieren Sie sie folgendermaßen:

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

Die Argumente, die Sie in Ihrem Tupel angeben, werden jetzt in der richtigen Reihenfolge angezeigt.Sie können jedoch immer noch Argumente auslassen (see FAQ) und Sie können nach wie vor zusätzliche Argumente geben, die Gewerkschaften der Typen in Ihrem Tupel sein:

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 

Das Beste, was ich tun kann danach ein Bündel anhängen ist von never auf Ihre Tupeln:

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

Jetzt zumindest zusätzliche Parameter wird dazu neigen, etwas sinnvoll eingegeben werden:

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

Okay, das ist das Beste, was ich tun kann. Ich hoffe es hilft. Viel Glück!

Verwandte Themen