typescript - Varför är typen {} (icke-nullvärden) tilldelningsbar till subtyper som { [key: string]: string }?


Typ {} hänvisar till icke-nullish värden; det hänvisar inte till objekt utan egenskaper.

Men jag märkte detta beteende:

const x: {} = 0; // okay, since 0 is a non-nullish value
const y: { [key: string]: string } = 0; // error, since 0 is not a { [key: string]: string }
const z: { [key: string]: string } = x; // okay, unexpected

Jag förväntade mig att TypeScript skulle ge ett fel för tilldelningen z, precis som det gör för tilldelningen y. Ändå typkontrolleras det. Är det ett fel?

Svar #1

Detta fungerar som avsett, även om det visar att tilldelningsbarhet inte är transitiv. Det vill säga, det finns typer A, B och C sådana att A extends B och B extends C men inte A extends C. Transitivitet av tilldelningsbarhet är ofta sann, men eftersom TypeScripts typ-system inte är helt ljudligt, finns det ställen där transitivitet misslyckas. Du har hittat ett av dem: number extends {} är sant, och {} extends {[key: string]: string} är sant, men number extends {[key: string]: string} är falskt. För andra exempel och diskussioner, se microsoft/TypeScript#42479 och microsoft/TypeScript#47331.


Så

const x: {} = 0;

är tillåtet eftersom {} inte har några kända egenskaper och därför kolliderar inga av 0:s uppenbara medlemmar med det.

Och

const y: { [key: string]: string } = 0; // error

är inte tillåtet eftersom inte varje uppenbart medlem av 0 kan tilldelas till string. Till exempel har toFixed-medlemmen typen (fractionDigits?: number | undefined) => string, vilket inte är en string. Så det uppfyller inte indexsignaturen.

Men varför är

const z: { [key: string]: string } = x; // okay

tillåtet?


Tja, den anonyma tomma objekttypen {} är inte ett gränssnitt, och därför kan den få en implicit indexsignatur. Gränssnitt får inte implicita indexsignaturer, som beskrivs i microsoft/TypeScript#15300 och diskuteras i designanteckningarna i microsoft/TypeScript#7059; gränssnitt nämns som motsats till objekttypdefinitioner, och utifrån kontexten kan vi dra slutsatsen att icke-gränssnitt objektstyper är objekttypdefinitioner. (Förmodligen har sådana typer en syntax som liknar en objekttypdefinition, med omgivande {⋯}, och objektvärden värden ges också sådana typer.)

Hur som helst, från handbokens beskrivning av implicita indexsignaturer:

En objektliteraltyp kan tilldelas en typ med en indexsignatur om alla kända egenskaper i objektliteralen kan tilldelas den indexsignaturen.

Eftersom x är av typen {}, jämförs då {} med { [key: string]: string }. Typen {} har inga kända egenskaper, så den kan enkelt tilldelas till string-indexsignaturen. Och därför matchar den implicita indexsignaturen och tilldelningen lyckas:

const z: { [key: string]: string } = x; // okay

Återigen finns det en osäkerhet här. Bara för att en objekttyp inte är känd för att konfliktera med en indexsignatur betyder det inte att det inte finns en konflikt. Saknade egenskaper är inte nödvändigtvis undefined. Detta kan vara vad du syftar på när du säger att en variabel av typen {} inte nödvändigtvis är ett tomt objekt. Den kan ha alla möjliga egenskaper, och dessa egenskaper kan konfliktera med vilken indexsignatur som helst. Ja, detta är osäkert. Men dessa tilldelningar är så bekväma att det skulle vara väldigt irriterande att använda den "säkra" versionen som ständigt ger fel, vilket faktiskt är anledningen till att implicita indexsignaturer infördes från början, för att sluta irritera utvecklare med säkerhetsregler som hämmade produktiviteten.

Inte alla håller med om sådana beslut, men vi kan åtminstone vara säkra på att, för de exempel som visas här, beter sig TypeScript exakt som avsett. Det är inte en bugg.

Svar #2

Jag tror inte att det är ett fel, eftersom när du säger:

const z: { [key: string]: string } = x;

Kompilern kontrollerar inte vad innehållet i x är utan kontrollerar vilken typ det har och dess typ kan faktiskt stämma överens. Till skillnad från den här raden:

const y: { [key: string]: string } = 0;

att kompilatorn omedelbart ska kunna känna igen att typerna inte matchar.

Relaterade artiklar