I'm trying to get better at Typescript types, and have a need for a type safe event emitter. I've tried many different ways now but I just can't seem to get the types to resolve quite right. Can you spot where I'm making a mistake?
In the example below I have an "Events" type that maps event names to the arguments that must be passed along with that event. So if I emit "Foo" I have to also pass a "bar" string, and the listener should know there's a "bar" property to read.
interface Events {
Foo: {
bar: string;
}
}
type EventKeys = keyof Events
class Emitter {
...
emit<K extends EventKeys> (title: K, value: Events[K]): void {
// With this signature I want to require if the caller specifies a title of "Foo"
// then they must specify value as "{bar: string}". This part looks to work great!
this.emitter.emit("connection", [title, value])
}
public on (listener: any): void {
// I use "any" here because this part of the code isn't super relevant to this example
this.emitter.on('connection', listener.event.bind(f))
}
}
class Listener {
...
event<K extends EventKeys> ([title, value]: [title: K, value: Events[K]]): void {
switch(title) {
case "Foo":
console.log(value)
// Here "value" is of type "Events[K]",
// which I take to mean it should know it's type "Events[Foo]"
// or actually "{bar: string}",
// but I don't get the autocompletion I expect.
break
}
}
}
Is it not possible to get {bar: string} from generics like Events[K]?