Vad är indexerbara typer i TypeScript?
Jag läste om indexerbara typer och 'ordbok'-mönstret förvirrade mig:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
interface StringArray {
[index: number]: string;
}
Vad är skillnaden mellan de två? Hur initierar man ens en variabel med typen NumberOrStringDictionary? Och varför beror NumberOrStringDictionary's fält (length och name) på index-signerat fält?
Hur skiljer sig NumberOrStringDictionary från detta:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
Svar #1
Den här typen:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number;
name: string;
}
innebär att ett värde av typen NumberOrStringDictionary måste innehålla en egenskap med namnet length av typen number och en egenskap med namnet name av typen string. Dessutom får ett värde av den typen inneha vilka som helst string-namngivna egenskaper, så länge värdena för sådana egenskaper kan tilldelas string | number.
Några punkter att klargöra:
Indexsignaturer får inte konfliktera med några andra nycklar. Till exempel är
length-egenskapen av typennumber, så den är kompatibel med indexsignaturen (numberkan tilldelas tillnumber | string). Ochname-egenskapen är också kompatibel. Om du försökte lägga till en egenskap av någon inkompatibel typ skulle du få ett fel:interface Oops extends NumberOrStringDictionary { acceptable: boolean; // error! //~~~~~~~~ <-- boolean not assignable to string | number }Om jag har en nyckel av typen string och jag läser egenskapen vid den nyckeln från ett värde av typen
NumberOrStringDictionary, vilken typ har den egenskapen? Den ärnumber | string. Föreställ dig nu vad som skulle hända om du tilläts lägga till en inkompatibel egenskap (somacceptable: boolean). I så fall skulle det inte längre vara sant. Om den nyckel jag väljer råkar vara"acceptable", skulle enbooleankomma ut vilket inte ärnumber | string, så något är fel. För att vara säker skulle du behöva förvänta dignumber | string | boolean.Du kan tänka på egenskaperna utan indexsignatur som specialfall av indexsignaturen, där du vet att egenskapsnyckeln definitivt finns och att värdet är av någon möjligtvis mer specifik typ. De kända egenskaperna bör betraktas som särskilda/special fall av indexeraren. "Alla egenskaper i detta objekt är av typen
string | number. Särskilt ärlength-egenskapen i detta objekt av typennumber, ochname-egenskapen i detta objekt är av typenstring". Det är som att säga "Mina husdjur är alla hundar. Fido här är en pudel. Inkompatibilitet skulle se ut som ”Mina husdjur är alla hundar. Fido här är en undulat”. Du kan säga det, men det är inte konsekvent. Du kanske tänker på indexsignaturer som ett ”standard” eller ”allt annat” eller ”rest”-fall (”Mina husdjur är alla hundar utom den här undulaten”), men det är inte vad det betyder i TypeScript. Det finns ett öppet ärende, microsoft/TypeScript#17867, som frågar efter en sådan konstruktion i språket, men den finns ännu inte (det finns lösningar).En index-signatur betyder generellt inte att alla
string-nycklade egenskaper kommer att visas. Du får lägga till så många eller så få sådana egenskaper som du vill. Det är därför möjligt att när du inspekterar en egenskap av typenNumberOrStringDictionary, kan den saknas. När du läser en sådan saknad egenskap, kommer du att få ett värde av typenundefined, inte avstring | number, men kompilatorn kommer att låtsas att det ärstring | number. TypeScript 4.1 introducerade en kompilatorflagga för att behandlaundefinedsom ett möjligt resultat av vilken index-signatur egenskapsläsning som helst, men den är inte på som standard.
Kanske skulle det vara hjälpsamt att gå igenom några exempel och se vad som accepteras och vad som avvisas av kompilatorn. Här är en giltig tilldelning:
const valid: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: "hey"
};
De erforderliga length och name egenskaperna finns där, och de ytterligare egenskaperna matchar indexsignaturen. Detta är också giltigt:
const alsoValid: NumberOrStringDictionary = {
length: 1,
name: "bob",
};
eftersom du inte behöver lägga till några sådana indexsignatur-egenskaper. Nu till misstagen:
const invalid1: NumberOrStringDictionary = { // error!
//~~~~~~~~ <- property "name" is missing
length: 1,
someOtherKey: 123,
someOtherKey2: "key"
};
Du kan inte utelämna en obligatorisk egenskap. Även:
const invalid2: NumberOrStringDictionary = {
length: "fred", // error!
//~~~~ <-- string is not assignable to number
name: "bob",
someOtherKey: 123,
someOtherKey2: "key"
};
Du kan inte sätta fel typ av den erforderliga egenskapen. Och slutligen:
const invalid3: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: true // error!
//~~~~~~~~~~~ <-- boolean is not string | number
};
Du kan inte lägga till en egenskap som kolliderar med indexsignaturen.
Playground-länk till kod
Svar #2
Objekt i JS kan tänkas som en ordbok. De kan ha godtyckliga nycklar, som motsvarar tillhörande värden. Denna objekt-ordbok-dualitet syns också i syntaxen:
const o = {key: 1};
console.log(o['key']);
console.log(o.key)
Egenskaper kan läggas till och tas bort i körning.
I allmänhet har de flesta objekt en konstant, känd uppsättning egenskaper, och du modellerar dem som vanliga gränssnitt i TypeScript.
Du behöver använda en indexerad typ om du vill modellera objekt som följer JS-regler
- kan innehålla vilken egenskap som helst
- kan ändras vid körning genom att lägga till och ta bort egenskaper
Observera att det finns 2 typer av index
- nummer - du kan endast indexera via ett nummer (vilket innebär att du använder dem som en array)
- string - du kan indexera via vilken sträng som helst (du använder dem som en dict)
I ditt exempel:
interface StringArray {
[index: number]: string; // number index
}
interface NumberOrStringDictionary {
[index: string]: number | string; // string index
length: number;
name: string;
}
Du definierar variablerna för dessa gränssnitt som följer:
const myArray: StringArray = ["Bob", "Fred"];
console.log(myArray[0])
const d: NumberOrStringDictionary = {
a: 1,
b: 2
length: 2,
name: 'myDict'
}
// You can add and remove properties in runtime
d.c = 1;
console.log(d['c'])
delete d.b;
console.log(d['c'])
Indexerade typer låter dig specificera typerna på värdena för egenskaperna.
const d1: NumberOrStringDictionary = {
a: true, // error: Type 'boolean' is not assignable to type 'string | number'.
b: {}, // error: Type '{}' is not assignable to type 'string | number'.
length: 1,
name: 'myDict'
}
Som beskrivs i länken från det ursprungliga inlägget, om du lägger till ytterligare egenskaper till en strängindexerad typ, måste typerna för dessa egenskaper vara kompatibla med den typ som anges för indexet.
