typescript - Villkorliga typer och typereduktion


Jag försöker lösa ett skrivproblem, här är ett 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

Som du kan se använder jag villkorliga typer för att begränsa typen av data i dispatchern. När jag anropar funktionen fungerar det korrekt, jag kan inte skicka en sträng när händelsen är connected eftersom det ska vara en boolean.

Men när jag implementerar dispatchern fungerar inte typavsmalning. Kompilatorn kan inte avgöra typen av data när jag kontrollerar typen av event.

Jag använder TS 4.8. Finns det en möjlig lösning som skulle kunna fungera i mitt fall? Jag har sökt och inte hittat något tillfredsställande än.

Svar #1

En lösning är att skapa en union av alla möjliga argumentpar som representeras som tupler. Här har jag valt att använda en avbildad typ:

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
}

Du kan också använda distributiva villkorstyper som @jcalz visade:

type PossibleArgs = EventObject extends infer E ? E extends EventObject ?
    [event: E["event"], data: E["data"]] : never : never;

Lekplats