typescript - Varför ger denna CustomExtract ett annat resultat än standard Extract?


Koden förklarar min fråga:

type A = {
  a: number,
} | null

// Extract as defined in lib.es5.d.ts
type Extract<T, U> = T extends U ? T : never;

type CustomExtract = A extends null ? A : never;

type Result1 = Extract<A, null> // null
type Result2 = CustomExtract;   // never

Extract och CustomExtract är samma kod, med skillnaden att Extract är en generisk typ.

Också, som ett relaterat exempel, string | null utvidgar inte null.

Så, hur fungerar typer egentligen under huven i det här ämnet? Jag kan föreställa mig att det kanske kör den generiska typen för varje typ i unionen och sedan unioniserar alla resultaten, men jag vill ha den riktiga tekniska definitionen och hur det verkligen fungerar.

Svar #1

Skillnaden är att Extract är en distributiv villkorlig typ, medan din CustomExtract inte är det.

För att en betingad typ av formen X extends Y ? A : B ska vara distributiv, måste den kontrollerade typen X vara en "naken typ-parameter"; det vill säga, en typ-parameter som T i interface Foo<T> {...}, och naken i det att det bara är typ-parameter som kontrolleras (dvs. T extends ...) och inte bara något uttryck som inkluderar typ-parametern (t.ex. Promise<T> extends ... eller [T] extends ...).

Som du misstänkte, utvärderas distributiva konditionstyper faktiskt som unionen av villkoret för varje unionselement av typen som kontrolleras T. Så om F<T> är en distributiv konditionstyp, kommer F<A | B | C> att utvärderas som F<A> | F<B> | F<C>. En potentiell fälla är att F<never> kommer att utvärderas som never oavsett detaljerna i F (så länge det är distributivt), eftersom never anses vara den "tomma unionstypen".

För mer information, se mitt andra svar om vad distributiva villkorstyper är och hur de fungerar.

Svar #2

Det @jcalz har sagt är helt korrekt, men jag ska försöka förklara detta på enkelt engelska.

Vad Extract<T, U> gör är att returnera delmängden av T som kan tilldelas U. Om T har några U-typer och några andra typer, får vi bara U-typerna. Varför och hur det gör detta, trots att det har en definition som ser likadan ut som CustomExtract, det är @jcalz:s svar.

I ditt fall är type A unionen av {a: number} och null. {a: number} kan inte tilldelas null, men null kan tilldelas null. Så delmängden som kan tilldelas null är null.

type CustomExtract=A extends null ? A : never; kommer aldrig att returnera en delmängd. Om villkoret uppfylls returnerar det A i sin helhet och om villkoret inte uppfylls returnerar det never.

För att X extends Y ska vara sant, måste alla värden av X kunna tilldelas till Y eller med andra ord, X måste vara lika med eller snävare än Y.

A extends null är falskt eftersom A är bredare än null. Å andra sidan är null extends A sant.

Relaterade artiklar