Hvordan få TypeScript til å typekontrollere objektlitteraler med dynamiske nøkler?
Jeg er litt forvirret over hvordan TypeScript utfører typekontroll når man bruker dynamiske nøkler i objektlitteraler. Vurder de følgende to funksjonene som returnerer en kopi av et objekt:
type Foo = {
a: number;
b: number;
};
const INIT_FOO: Foo = { a: 0, b: 0 };
function test1(k: keyof Foo) {
const f: Foo = { ...INIT_FOO, [k]: true };
return f
}
function test2(k: keyof Foo) {
const f: Foo = { ...INIT_FOO };
f[k] = true;
return f
}
TypeScript-kompilatoren vil kun rapportere feil for funksjon test2, og ikke funksjon test1.
Hvorfor rapporterer ikke kompilatoren en feil for funksjonen test1 når den tydeligvis er feil?
Svar #1
Dette er et kjent problem, rapportert i microsoft/TypeScript#38663. Det skjer fordi beregnede egenskaper der nøkkelen er en union av strengliteralene (som keyof Foo) utvides til en string-indeksignatur. Det er ikke nøyaktig feil, men det er ikke spesifikt nok til å være nyttig. Denne oppførselen er enten en feil, som rapportert i microsoft/TypeScript#13948, eller muligens en designbegrensning, som beskrevet i microsoft/TypeScript#21030. Uansett, dette er den nåværende oppførselen i språket.
Når den beregnede egenskapen utvides til en indeks-signatur, blir det umulig å fange feilen. Indeks-signaturer regnes ikke som overskytende egenskaper; typen {a: number, b: number} & {[k: string]: boolean} (som omtrent er det du får her) kan tildeles {a: number, b: number}, så ingen feil rapporteres. Men selvfølgelig, i realiteten har du tildelt en boolean til en av a eller b-egenskapene; det er bare det at kompilatoren ikke kan se det på grunn av utvidelsen med indeks-signaturen.
Bortsett fra bare å notere dette problemet og være forsiktig, kan den andre tingen du gjøre være å manuelt implementere en funksjon som produserer typen som "bør" genereres når du har en beregnet egenskap av en forening av nøkkeltyper:
function computedProp<K extends PropertyKey, V>(
key: K, val: V
): K extends any ? { [P in K]: V } : never {
return { [key]: val } as any;
}
Returverdien er en distribuerbar betinget type som produserer en forening av objekttyper, slik som dette:
const example = computedProp(Math.random() < 0.5 ? "a" : "b", true);
// const example: { a: boolean; } | { b: boolean; }
Unionen {a: boolean} | {b: boolean} er en mer nøyaktig representasjon av typen til {[k]: true}. Hvis vi bruker det, får du den forventede feilen:
function test1(k: keyof Foo) {
const f: Foo = { ...INIT_FOO, ...computedProp(k, true) }; // error!
// ~
// '{ a: boolean; b: number; } | { b: boolean; a: number; }' is not assignable to 'Foo'.
return f
}
Å bruke en funksjon i stedet for en direkte beregnet egenskap er åpenbart ikke særlig ønskelig, men i det minste kan du på en måte opprettholde typesikkerhet hvis det er viktig for deg.
Lenke til kode på lekeplass
