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

Relaterte artikler