I'm currently in the process of transitioning from JS to TS and can't quite get my head around keyof
. I found https://github.com/kimamula/TypeScript-definition-of-EventEmitter-with-keyof and now I'm trying to implement this. All went fine until I turned on 'noImplicitAny'.
I have made an example in the ts playground to show the issue I'm having:
In the constructor there's no access to K
so I had to explicitly make the Map (arg: any)
to get it to work.
Is there a solution that avoids using any when creating the Map?
The problem is that you are trying to assign (arg: keyof T) => any
to (arg: T[K]) => any
. keyof T
represents any property name of T
For example in this class:
class X { test: string; test2: number; }
keyof X
is the same as 'test' | 'test2'
while T[K]
with K extends keyof T
represents the type of the property of T
with the name K
, so for the class above T[K]
can be either number
or string
depending on the value passed for K
So if you have Emitter<X>
you would basically trying to assign (arg: string | number) => any
to (arg: 'test' | 'test2') => any
which is definitely not type safe.
My guess is that you want to constrain listener
to have an argument with the same property type as a member of the class. For that you should use Map<string, ((arg: T[keyof T]) => any) []>
to have arg
constrained to any possible property type of T
class EventEmitter<T> {
private events: Map<string, ((arg: T[keyof T]) => any) []>;
constructor() {
this.events = new Map<string, ((arg: T[keyof T]) => any) []>();
}
public getOrCreateEvents<K extends keyof T>(event: K, listener: (arg: T[K]) => any): ((arg: T[K]) => any) [] {
return this.events.get(event) || this.events.set(event, new Array<(arg: T[K]) => any>()).get(event);
}
}
// Test
class X { test: string; test2: number; }
var d = new EventEmitter<X>();
d.getOrCreateEvents("test2", c => console.log(c));// c is infered to string
d.getOrCreateEvents("test2", c => console.log(c)); // c is infered to number