De ce acest fragment permite TypeScript să deducă un generic dintr-o funcție „părinte”?


Se pare că există o funcționalitate nedocumentată care permite funcțiilor să deducă un tip dintr-o funcție în care sunt transmise ca argument.

Încerc să înțeleg de ce funcționează asta și dacă este intenționat.

Teren de joacă

Dat un Model, atunci cu o funcție generică update și apply, pot deduce un tip din apply atunci când folosesc rezultatul lui update ca argument cu trucul T extinde infer U ? U : never:

interface Model {
    foo: number
    bar: string
}

function apply<T>(fn: (t: T) => T) {};

function whyDoesThisWork() {

    function update<T>(u: T extends infer U ? Partial<U> : never) { return (t: T) => ({ ...t, ...u }) };

    // Somehow, Typescript is able to infer that we want Model to be the generic supplied for update
    apply<Model>(update({ foo: 1 }));

}

function thisDoesntWorkForObviousReasons() {

    function update<T>(u: Partial<T>) { return (t: T) => ({ ...t, ...u }) }

    // update infers T as { foo: 1 }, because that makes sense
    apply<Model>(update({ foo: 1 }));

}

Răspuns #1

Acest comportament apare din cauza tipizării contextuale.

„Trucul” poate fi extras într-un tip ajutător numit DontInfer:

type DontInfer<T> = T extends infer S ? S : never;

Când DontInfer este utilizat într-o semnătură de funcție generică, T nu va fi dedus acolo unde este folosit. Din exemplul meu, argumentul generic u poate fi considerat de forma Partial<T>, dar T nu va fi dedus de u:

function update<T>(u: DontInfer<Partial<T>>) { return (t: T) => ({ ...t, ...(u as Partial<T>) }) };

Acest lucru înseamnă că funcția update nu va putea să deducă T dacă este folosită de una singură:

// T is inferred to be `unknown`
update({ foo: 1 })

Dar atunci când este folosit într-un context, T poate fi dedus:

// T is inferred to be Model
setState<Model>(update({ foo: 1 }));

DontInfer funcționează în acest scop deoarece amână evaluarea tipurilor condiționale nerezolvate.