typescriptconditional-typestype-narrowing

Conditional types and type narrowing


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.


Solution

  • 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;
    

    Playground