Issue
I have a scenario where I have different events like the following:
type EventType = 'foo' | 'bar'
type Event = {
type: 'foo',
timestamp: number,
payload: { a: number }
} | {
type: 'bar',
timestamp: number,
payload: { b: string }
}
Then I have a listener like this:
on('foo', event => {
// Here we should know that the type must be 'foo',
// and therefore the payload has the shape {a: number}
// but I don't know how to tell that to TS
})
I've tried a few different things, but so far all I've managed is for the compiler to stop compiling 🥲
I thought this question could help, but I didn't manage to get it working. I think the problem is that I'm using a literal union instead of an enum.
I imagine this is a situation happening in many places, so I was hoping to find a solution more easily.
Solution
My recommendation is to make on()
a generic function in the type K
of its first argument, and then write the type of the event
callback parameter in terms of that.
To that end I would first write a helper type called EventMap
like this:
type EventMap = { [E in Event as E['type']]: E };
which remaps Event
to an object type whose keys are the intended first argument to on()
and whose values are the type of the event
callback parameter, like this:
/* type EventMap = {
foo: {
type: 'foo';
timestamp: number;
payload: {
a: number;
};
};
bar: {
type: 'bar';
timestamp: number;
payload: {
b: string;
};
};
} */
Armed with that type, on()
's call signature can be written as:
declare function on<K extends keyof EventMap>(
type: K,
cb: (event: EventMap[K]) => void
): void;
So the event
callback parameter is of the indexed access type EventMap[K]
, which is the value type of EventMap
at key K
.
Let's test it out:
on('foo', event => {
event.payload.a.toFixed(2); // okay
});
on('bar', event => {
event.payload.b.toUpperCase(); // okay
});
Looks good!
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.