typescript - Å lage objektliterale med generisk nøkkel mislykkes ved kompilering


Jeg prøver å gjøre følgende

function createRecord<T extends string>(key: T) : Record<T, T> {
    return {[key]: key}; // Type '{ [x: string]: T; }' is not assignable to type 'Record<T, T>'

}

Det ser imidlertid ut til at den beregnede egenskapen blir tolket som en strengltype av TypeScript-kompilatoren i stedet for strengbokstavtypen T. Er det mulig å implementere funksjonen ovenfor uten noen typepåstander?

Svar #1

Det finnes en langvarig åpen sak på microsoft/TypeScript#13948 som påpeker at beregnede egenskapsnøkler ofte utvides til string, og at dette ofte ikke er det folk ønsker. Ting fungerer fint når din beregnede nøkkel er av en enkelt strengliteraltype:

const a = "a";
const x = { [a]: "" } // {a: string}, okay

Men så snart du har en unionsnøkkel

const ab = Math.random() < 0.5 ? "a" : "b" // const ab: "a" | "b"
const y = { [ab]: "" } // { [x: string]: string}, 😢

eller en generell nøkkel

// (parameter) k: K extends PropertyKey
const z = { [k]: "" } // { [x: string]: string}, 😢

typen blir utvidet til en strengindeks-signatur.

Det problemet er fortsatt åpent, uten noen åpenbar løsning i sikte. Lignende problemer har blitt lukket som designbegrensninger; se microsoft/TypeScript#47703 for et ganske nylig eksempel.


Det betyr at, for nå, hvis du vil se kompilatoren behandle objekter med beregnede nøkkelverdier som en annen type, må du bruke noe som en typepåstand for å overbevise den om å gjøre det. Mitt forslag for din createRecord() ville være

function createRecord<K extends string>(key: K) {
  return { [key]: key } as { [P in K]: Record<P, P> }[K]
}

Dette oppfører seg på samme måte som Record<K, K>:

const aRec = createRecord("a"); // Record<"a", "a">

men du kan se en forskjell når typen til key er en union:

const abRec = createRecord(Math.random() < 0.5 ? "a" : "b")
// const abRec: Record<"a", "a"> | Record<"b", "b">

Hvis du sender inn enten "a" eller "b", ønsker du at abRec skal være enten {a: "a"} eller {b: "b"}. Hvis du hadde latt det være Record<K, K> der K er "a" | "b", ville du fått typen {a: "a" | "b"; b: "a" | "b"}, noe som ikke er passende siden det antyder at begge nøklene er til stede.

Lenke til kode på lekeplassen

Relaterte artikler