I'm trying to solve a typing problem, here is a MWE:
// Typing
export type EventObject = {
event: 'connected';
data: boolean;
} | {
event: 'more';
data: string;
};
export type EventData<T, Event> = T extends { event: Event, data: unknown } ? T['data'] : never;
// Code
function dispatch<E extends EventObject['event']>(event: E, data: EventData<EventObject, E>) {
switch(event) {
case 'more':
const res = data.indexOf('xxxx'); // Compiler issue: it does not know the type of data
break;
default:
break;
}
}
dispatch('more', true); // The compiler works properly and tells me that data should by a string
As you can see, I'm using conditional types to restrict the type of data
in the dispatcher. When I call the function, it works properly, I cannot pass a string when the event is connected
since it should be a boolean
.
But, when I implement the dispatcher, type narrowing does not work. The compiler is unable to determine the type of data
when I check the type of event
.
I'm using TS 4.8. Is there a possible workaround that could work in my case? I've searched and found nothing satisfying yet.
One solution is to create a union of all the possible argument pairs represented as tuples. Here I have chosen to use a mapped type:
type PossibleArgs = {
[K in EventObject["event"]]: [
event: K,
data: Extract<EventObject, { event: K }>["data"]
];
}[EventObject["event"]];
function dispatch(...args: PossibleArgs) {
const [event, data] = args;
if (event === "more") data.indexOf("XXX"); // OK
else if (event === "connected") data; // boolean
else data; // never
}
You can also utilize distributive conditional types as @jcalz demonstrated:
type PossibleArgs = EventObject extends infer E ? E extends EventObject ?
[event: E["event"], data: E["data"]] : never : never;