Home > Back-end >  Conditional types and type narrowing
Conditional types and type narrowing

Time:09-08

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.

CodePudding user response:

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

  • Related