typescript - Adding definitions for strongly typed events -
i'm trying figure out how efficiently add typed events project, keep running odd issues. ideally i'd able this:
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);
the first issue apparently tuples aren't valid types rest parameters, despite being arrays of typed elements under hood (i believe being worked on). suppose that's fine if forgo typing emit
method, , make events map function types instead of tuples. give benefit of little information arguments are.
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) => {});
at point i'm stumped. intellisense can infer proper signatures on
or once
, actual arguments types inferred event arguments on callback, makes no sense me. opened an issue few days ago, have yet response. i'm unsure if bug, or if i'm overlooking something.
in meantime, there efficient ways of doing this? i've thought adding overloads emitter class (here eventemitter
using node typings):
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); }
however requires me have actual implementation of on
in class, , if want types once
or emit
have duplicate of event definitions. there better solution?
i commented on reported issue; seem buggy. work around annotating types on function parameters. it's annoying works:
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 expected myemitter.on('eventa', (str: string, num: number, boo: boolean) => {}); // error expected
you're right can't use tuple types rest parameters. can kind of work around converting tuple array, forgets order:
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 string|number
you can closer figuring out reasonable maximum number of function parameters (say 4) , declaring them way:
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; }
the arguments specify in tuple appear in right order. however, can still leave out arguments (see faq) , can still specify extra arguments unions of types in tuple:
myemitter.emit('eventa', 1, 2); // error expected myemitter.emit('eventa', 'one', 2); // works myemitter.emit('eventa', 'one'); // works, args optional myemitter.emit('eventa', 'one', 2, 3) // works, because subsequent args union myemitter.on('eventa', (str, num) => { }); // works myemitter.on('eventa', (str) => { }); // above myemitter.on('eventa', (str, num, rando) => { }); // above, rando string | number
the best can after append bunch of never
tuples:
interface myevents { 'eventa': [string, number, never, never, never, never, never]; 'eventb': [string, { prop: string, prop2: number }, (arg: string) => void, never, never, never, never]; }
now @ least parameters tend typed usefully:
myemitter.emit('eventa', 'one', 2, 3) // error on 3, should undefined myemitter.on('eventa', (str, num, rando) => { }); // rando never
okay, that's best can do. hope helps. luck!
Comments
Post a Comment