Typescript: hur man förklarar denna interaktion mellan extends och funktionstyp


Exempel 1 är helt logiskt för mig

type X = (1 | 2 | 3) extends (infer I) ? [I] : never;

// X = [1 | 2 | 3]

Exempel 2 Jag vet inte varför typvariablerna nu är korsade

type X = (
    ((_: 1) => void) | ((_: 2) => void) | ((_: 3) => void)
) extends ((_: infer I) => void) ? [I] : never;

// X = [1 & 2 & 3]

Min gissning är att det har någon relation/likhet med detta:

type X = { x: number } | { y: number } | { z: number };
type Y = keyof X;

// Y = x & y & z

Jag kan dock inte övertyga mig själv om att jag förstod det från första princip. Skulle gärna vilja höra hur detta kan förklaras.

Svar #1

Det förklarades i versionsnoterna när typinferens från villkorliga typer introducerades. Om det blir en union eller en intersection beror på variansen hos den infererade typen. Intuitivt, om en typ infereras som en gemensam typ för flera värden, kan det vara vilken som helst av dem (union), men om den infereras som argumenttyp för flera funktioner måste den vara accepterad av alla dem (intersection).

Citat från versionsinformationen:

Följande exempel visar hur flera kandidater för samma typvariabel i kovenanta positioner orsakar att en unionstyp härleds:

type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>;  // string
type T11 = Foo<{ a: string, b: number }>;  // string | number

På samma sätt orsakar flera kandidater för samma typvariabel i kontravarianta positioner att en snitt-typ härleds:

type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number

Vidare diskussion kan hittas i PR som implementerar denna funktion.

Som en parentes gör det möjligt några häftiga trick som konvertering från unionstyp till intersectionstyp.

Relaterade artiklar