typescript - Hur man förstår relationerna mellan typerna any, unknown, {} och mellan dem och andra typer?
Försöker förstå relationer mellan typer har jag denna kod
type CheckIfExtends<A, B> = A extends B ? true : false;
type T1 = CheckIfExtends<number, unknown>; //true
type T2 = CheckIfExtends<number, {}>; //true
type T3 = CheckIfExtends<number, any>; //true
type T4 = CheckIfExtends<() => void, unknown>; //true
type T5 = CheckIfExtends<() => void, {}>; //true
type T6 = CheckIfExtends<() => void, any>; //true
type T7 = CheckIfExtends<unknown, any>; //true
type T8 = CheckIfExtends<any, unknown>; //true
type T9 = CheckIfExtends<{}, unknown>; //true
type T10 = CheckIfExtends<{}, any>; //true
type T11 = CheckIfExtends<any, {}>; //boolean
type T12 = CheckIfExtends<unknown, {}>; //false
Länk till lekplats
Kan någon förklara detta? Vilken skillnad? Hur är det möjligt att any extends {} och any inte extends {} samtidigt? Om any extends unknown och unknown extends any, betyder det då att de är starkt lika? Är det ett nytt fel i Typescript ovanpå null och undefined-likhetsproblemet i JavaScript?
Faktiskt,
type T = CheckIfExtends<any, number>; //boolean
Svar #1
Skillnaden är i huvudsak denna:
typen
anyär medvetet osäker, eftersom den kan tilldelas både till och från vilken annan typ som helst (med möjligt undantag förnever, beroende på var du använder den). Osäkerhet betyder att några grundläggande regler för typer bryts, såsom transitivitet av subtypning. Generellt, omAkan tilldelasB, ochBkan tilldelasC, då kanAtilldelasC. Menanybryter detta. Till exempel:stringkan tilldelasany, ochanykan tilldelasnumber... menstringkan inte tilldelasnumber. Denna särskilda osäkerhet är mycket användbar, eftersom den gör att vi i princip kan "stänga av" typkontroll i en del av koden som antingen är svår eller omöjlig att typa korrekt. Men du måste vara mycket försiktig när du tänker påanysom en typ; det är mer som en "icke-typ".den tomma typen,
{}, är en typ som kan behandlas som ett objekt vid körning (det vill säga något du kan läsa egenskaper eller metoder från utan ett körfel), men den har inga kända egenskaper vid kompileringstid. Det betyder inte att den saknar egenskaper; det betyder bara att kompilatorn inte känner till några av dem. Detta innebär att endastnullochundefinedinte kan tilldelas till{}(null.fooellerundefined.fooär körfel). Även primitiva typer somstringkan behandlas som om de har egenskaper och metoder vid körning ("".lengthoch"".toUpperCase()fungerar, och även"".fooreturnerar baraundefined). Och naturligtvis kan vilken faktisk objekttyp som helst också tilldelas till{}.Å andra sidan är typen
{}inte tilldelningsbar till särskilt många typer. Om jag har ett värde av typen{}och försöker tilldela det till en variabel av typen{foo: string}, kommer det att uppstå ett kompilatorfel, eftersom{}inte är känt för att innehålla enfoo-egenskap. Du kan tilldela{}till sig själv, eller till en bredare typ somunknown, eller till "icke-typen"any.Detta gör
{}väldigt nästan till en topptyp, vilket är en typ som alla andra typer kan tilldelas. Det är i princip en topptyp mednullochundefinedborttagna.typen
unknownintroducerades i TypeScript 3.0 och är den sanna topptypen; varje typ i TypeScript kan tilldelasunknown. Ävennullochundefinedkan tilldelasunknown.Återigen, å andra sidan är
unknownendast tilldelningsbar till sig själv och den "icke-typen"any. Även typen{}är inte tillräckligt bred för att du ska kunna tilldelaunknowntill den. Istället ärunknowni huvudsak ekvivalent med{} | null | undefined(och betraktas som sådan sedan TypeScript 4.8)
De flesta av dina CheckIfExtends<A, B>-resultat kan förklaras av ovanstående. Undantaget är T11:
type T11 = CheckIfExtends<any, {}>; //boolean
Din CheckIfExtends<A, B>-typdefinition är en distribuerbar villkorstyp, som gör några intressanta saker när A är en unionstyp, eftersom den tillåter båda grenarna av villkoret att väljas om delarna av unionen uppfyller båda grenarna. Den gör också samma distribution när A är any, förutom när B är any eller unknown (så T8 beter sig normalt). Det finns viss diskussion om detta i microsoft/TypeScript#27418. Hur som helst, T11 tar båda grenarna och du får true | false, vilket är boolean. (Från microsoft/TypeScript#27418, unknown i A-positionen distribueras inte, så T7 och T12 beter sig också normalt).
Svar #2
Jag kommer att börja med vad extends betyder i TypeScript. Vid första anblicken ter det sig märkligt, eftersom det för produkttyper (som objekt) beter sig som 'är en övermängd' och för unions som 'är en delmängd', dessutom fungerar det helt annorlunda för funktionstyper. Det kan se märkligt ut vid första ögonblicket, men det är ett logiskt beteende, med andra ord har de flesta typer egenskapen soundness.
Min tumregel för att förstå detta koncept är att läsa extends som kan tilldelas till. Då om x extends y, betyder det att x kan användas närhelst y krävs.
Låt oss överväga tre olika algebraiska datatyper, om det ovanstående gäller för dem.
För produkttyp
type A = {a: string}
type B = {a: string; b: number}
type BextendsA = B extends A ? true : false // evaluates to true
Ovanstående är sant eftersom B kan användas på alla platser där A krävs, eftersom B täcker hela den struktur som A har. B är en superset av A. Men det som gäller här är att B kan tilldelas A.
För unionstyp
type A = number | string
type B = number
type BextendsA = B extends A ? true : false // evaluates to true
Totalt annorlunda ser det ut för union. B för produkt var en superset, för union är B en delmängd! Ja, olika logik, B representerar inte alla möjliga värden i A, men närhelst A behövs kan B användas istället. Så B kan tilldelas till A.
För funktionstyper
type A = (a: number) => void
type B = () => void
type BextendsA = B extends A ? true : false // evaluates to true
För funktionstyper ser det ännu konstigare ut eftersom A ser ut som en mer specificerad funktion än B, så hur kan B då utvidga A? Detta går tillbaka till tilldelningsbarhet, eftersom när något behöver A kan vi tilldela B. Detta är mycket tydligt i exempel som Array.map. Tänk på:
[1,2].map(x => x + 1)
Array.map kräver en funktion som har tre argument - (el,index,arr) => any men kan fungera med en funktion som bara har ett argument el => any. Och återigen gäller tilldelningsbarheten, B kan tilldelas A.
Typer som any, unknown, {} är osäkra, vilket betyder att deras beteende inte kan bevisas logiskt. Att förstå hur de beter sig i TS handlar mer om att förstå specifikationen och resonera kring sådana beslut. Men det kan inte förklaras logiskt, eftersom osäkra typer beter sig mot logik.
De platser där TypeScript tillåter osäkert beteende har noggrant övervägts, och genom hela detta dokument kommer vi att förklara var dessa inträffar och de motiverande scenarierna bakom dem.
