Let's say I'm building an event manager for a chat app. I've used event maps before and had a good time with it, so I will try it again.
This is the event map:
interface ChatEventMap {
incomingMessage: string;
newUser: {
name: string;
id: number;
};
}
type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];
So, to listen to an event, we have an on function that receives two args: the type (or name) of the event, and a callback that is called passing the event data.
It's something like this:
function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
// do stuff
}
on('newUser', user => {
console.log(user.name); // No errors!
});
But now I have the need to listen to ALL events at once. I thought of making an onEvent function that receives only a callback with the event type and it's data.
The problem is that inside the callback function, type guard is not working!
function onEvent(callback: <T extends EvType>(
ev: { type: T; data: ChatEventMap[T] },
) => void) {
// do stuff
}
onEvent(ev => {
if (ev.type === 'newUser') {
console.log(ev.data.name); // Error: Property 'name' does not exist on type 'ChatEventMap[T]'
}
});
What am I doing wrong?
TS Playground
Here you have working code
interface ChatEventMap {
incomingMessage: string;
newUser: {
id: number;
name: string;
};
}
type EvType = keyof ChatEventMap;
type Ev<T extends EvType> = ChatEventMap[T];
// a bit of love for TypeScript
type Mapped = {
[P in keyof ChatEventMap]: {
type: P, data: ChatEventMap[P]
}
}
function on<T extends EvType>(type: T, callback: (data: Ev<T>) => void) {
// do stuff
}
on('newUser', user => {
console.log(user.name); // No errors!
});
function onEvent(callback: (ev: Mapped[EvType]) => void) { }
onEvent(ev => {
if (ev.type === 'newUser') {
console.log(ev.data.name); // ok
}
if (ev.type === 'incomingMessage') {
console.log(ev.data); // ok, string
}
});
Sometimes, generics are not the best solution.
Please keep in mind, that next two functions are not the same:
function onEvent(callback: <T extends EvType>(ev: Mapped[T]) => void) { }
function onEvent(callback: (ev: Mapped[EvType]) => void) { }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With