Je možné odvodiť kľúče záznamu (Record) v TypeScripte?


Používam JSS a chcem definovať objekt style s kľúčmi a hodnotami s pevne stanoveným typom, bez toho aby som musel kľúče definovať dvakrát.

Prvý pokus:

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

Teraz style nie je silne typovaný, takže kompilátor nevydáva žiadne varovanie pri prístupe k style.nonExistingKey.

Druhý pokus:

Ak zadám kľúče explicitne takto:

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

potom dostanem silne typovaný rekord, t. j. style.nonExistingKey vyhodí chybu. Táto metóda však vyžaduje zdvojovanie kľúčov záznamu, pretože musia byť explicitne pridané ako generický argument.

Tretí pokus:

Môžem vytvoriť silne typovaný záznam aj po fakte pomocou nasledujúceho kódu:

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

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

Avšak potom stratím kontrolu typov pre hodnoty CSSProperties v zázname, takže toto nie je dobré riešenie.

Existuje spôsob, ako urobiť niečo takéto:

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

a mať T automaticky odvodzené ako 'root' | 'title' | ... atď.?

Odpoveď č. 1

S TypeScript 4.9 si myslím, že toto je konečne možné dosiahnuť použitím nového operátora satisfies:

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)

detské ihrisko

Ako je uvedené v príkladoch vyššie, toto zachováva silné typovanie všetkých kľúčov a hodnôt v objekte a zabraňuje pridávaniu vlastností, ktoré nespĺňajú typovanie.

Odpoveď č. 2

Môžete použiť pomocnú funkciu pri definovaní objektu. Funkcia bude mať typové parametre, ktoré vyžadujú, aby všetky vlastnosti boli typu CSSProperties s použitím indexového podpisu. Keďže je funkcia generická, výsledok bude správne typovaný (skutočne nebude mať indexový podpis, ale iba definované vlastnosti)

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

Môžete to tiež napísať tak, aby sa vrátil Record, ale nemyslím si, že by to prinieslo nejakú hodnotu

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

Súvisiace články