Er det mulig å slutte seg til nøklene til en Record i TypeScript?


Jeg bruker JSS og vil definere et style-objekt med både sterkt typede nøkler og verdier, uten å definere nøklene to ganger.

Første forsøk:

const style: Record<string, CSSProperties> = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

Nå er style ikke sterkt typet, så kompilatoren gir ingen advarsel når man får tilgang til style.nonExistingKey.

Andre forsøk:

Hvis jeg spesifiserer nøklene eksplisitt slik:

const style: Record<'root' | 'title' | ... , CSSProperties> = {
  root: {
    background: 'red'
  },
  ...
}

da får jeg en sterkt typet Record, dvs. style.nonExistingKey vil kaste en feil. Denne metoden krever imidlertid at man dupliserer record-nøklene, ettersom de må legges til eksplisitt som et generisk argument.

Tredje forsøk:

Jeg kan opprette en sterkt typet Record i etterkant med følgende kode:

const styleObj = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

const style = styleObj as Record<keyof typeof styleObj, CSSProperties>

Men da mister jeg typekontroll for CSSProperties-verdiene i posten, så dette er ikke en god løsning.

Finnes det en måte å gjøre noe slik som dette på:

const style: Record<T, CssProperties> = {
  root: {
    background: 'red'
  },
  ...
}

og la T bli automatisk antatt som 'root' | 'title' | ... osv.?

Svar #1

Med TypeScript 4.9 tror jeg at dette endelig kan oppnås ved å bruke den nye satisfies-operatoren:

const style = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  invalid: {
    invalidProperty: '' // Type '{ invalidProperty: string; }' is not assignable to type 'CSSProperties'.
                        // Object literal may only specify known properties, and 'invalidProperty' does not exist in type 'CSSProperties'.(2322)
  }
} satisfies Record<string, CSSProperties>;

type keys = keyof typeof style;  // "root" | "title" | "invalid"

style.nonExistingKey; // Property 'nonExistingKey' does not exist on type '{ ... }'.(2339)

style.root.fontWeight; // Property 'fontWeight' does not exist on type '{ background: string; }'.(2339)

lekeplass

Som vist i eksemplene ovenfor, beholder dette sterk typifisering av alle nøkler og verdier i objektet, og forhindrer å legge til egenskaper som ikke tilfredsstiller typifiseringen.

Svar #2

Du kan bruke en hjelpefunksjon når du definerer objektet. Funksjonen vil ha typeparametere som krever at alle egenskaper må være av typen CSSProperties ved bruk av en indeksignatur. Siden funksjonen er generisk, vil resultatet få riktig type (det vil faktisk ikke ha en indeksignatur, men bare de definerte egenskapene).

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T)  {
  return cfg;
}
const style = createStyleMap({
  root: {
  background: 'red'
  },
  title: {
    fontWeight: 'bold'
  }
});

style.root //ok
style['hu'] // error

Du kan også skrive det til å returnere Record, men jeg tror ikke dette ville tilføre noen verdi

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T) : Record<keyof T, CSSProperties> {
  return cfg;
}

Relaterte artikler