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.