typescript - Hva er Record-typen?


Hva betyr Record<K, T> i Typescript?

Typescript 2.1 introduserte Record-typen, og beskrev den i et eksempel:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

se Typescript 2.1

Og siden Avanserte typer nevner Record under overskriften Mapped Types sammen med Readonly, Partial og Pick, i det som ser ut til å være dens definisjon:

type Record<K extends string, T> = {
    [P in K]: T;
}

Readonly, Partial og Pick er homomorfe, mens Record ikke er det. Et tegn på at Record ikke er homomorf, er at det ikke tar en inputtype for å kopiere egenskaper fra:

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

Og det er det. Bortsett fra sitatene ovenfor, er det ingen annen omtale av Record på typescriptlang.org.

Spørsmål

  1. Kan noen gi en enkel definisjon på hva Record er?

  2. Er Record<K,T> bare en måte å si «alle egenskaper på dette objektet vil ha typen T»? Sannsynligvis ikke alle egenskaper, siden K har et eller annet formål...

  3. Forbyr den generiske K ekstra nøkler på objektet som ikke er K, eller tillater den dem og indikerer bare at deres egenskaper ikke blir transformert til T?

  4. Med det gitte eksempelet:

    type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
    

    Er det helt det samme som dette?

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
    

Svar #1

  1. Kan noen gi en enkel definisjon på hva Record er?

En Record<K, T> er en objekttype hvis egenskapsnøkler er K og hvis egenskapsverdier er T. Det vil si at keyof Record<K, T> er ekvivalent med K, og Record<K, T>[K] er (i hovedsak) ekvivalent med T.

  1. Er Record<K,T> bare en måte å si "alle egenskaper på dette objektet vil ha typen T"? Sannsynligvis ikke alle objekter, siden K har en viss hensikt...

Som du påpeker, K har et formål... å begrense egenskapstastene til bestemte verdier. Hvis du vil akseptere alle mulige strengeverdiefelter, kan du gjøre noe som Record<string, T>, men den idiomatiske måten å gjøre det på er å bruke en indekssignatur som { [k: string]: T }.

  1. Forbyr den generiske K ekstra nøkler på objektet som ikke er K, eller tillater den dem og indikerer bare at deres egenskaper ikke blir transformert til T?

Det «forbyr» ikke akkurat ekstra nøkler: tross alt er det generelt tillatt for en verdi å ha egenskaper som ikke er eksplisitt nevnt i typen dens... men den ville ikke gjenkjenne at slike egenskaper eksisterer:

declare const x: Record<"a", string>;
x.b; // error, Property 'b' does not exist on type 'Record<"a", string>'

og det ville behandle dem som overflødige egenskaper som noen ganger blir avvist:

declare function acceptR(x: Record<"a", string>): void;
acceptR({a: "hey", b: "you"}); // error, Object literal may only specify known properties

og noen ganger akseptert:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Med det gitte eksempelet:

     type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
    

    Er det helt det samme som dette?

     type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
    

Ja!

Svar #2

En Record lar deg opprette en ny type fra en Union. Verdiene i Union brukes som attributter for den nye typen.

For eksempel, si at jeg har en union som dette:

type CatNames = "miffy" | "boris" | "mordred";

Nå vil jeg lage et objekt som inneholder informasjon om alle kattene, jeg kan lage en ny type ved å bruke verdiene i CatNames-unionen som nøkler.

type CatList = Record<CatNames, {age: number}>

Hvis jeg vil tilfredsstille denne CatList, må jeg lage et objekt som dette:

const cats: CatList = {
  miffy: { age:99 },
  boris: { age:16 },
  mordred: { age:600 }
}

Du får veldig sterk typesikkerhet:

  • Hvis jeg glemmer en katt, får jeg en feil.
  • Hvis jeg legger til en katt som ikke er tillatt, får jeg en feil.
  • Hvis jeg senere endrer CatNames, får jeg en feil. Dette er spesielt nyttig fordi CatNames sannsynligvis importeres fra en annen fil, og sannsynligvis brukes mange steder.

Ekte verdens React-eksempel.

Jeg brukte dette nylig for å lage en Status-komponent. Komponentet ville motta en status-prop, og deretter rendre et ikon. Jeg har forenklet koden ganske mye her for illustrasjonsformål

Jeg hadde en union som dette:

type Statuses = "failed" | "complete";

Jeg brukte dette for å lage et objekt som dette:

const icons: Record<
  Statuses,
  { iconType: IconTypes; iconColor: IconColors }
> = {
  failed: {
    iconType: "warning",
    iconColor: "red"
  },
  complete: {
    iconType: "check",
    iconColor: "green"
  };

Jeg kunne da rendre ved å dekonstruere et element fra objektet inn i props, slik:

const Status = ({status}) => <Icon {...icons[status]} />

Hvis Statuses-unionen senere utvides eller endres, vet jeg at Status-komponenten min vil feile ved kompilering, og jeg vil få en feil som jeg kan fikse umiddelbart. Dette lar meg legge til flere feilsituasjoner i appen.

Merk at den faktiske appen hadde dusinvis av feilmeldinger som ble referert til mange steder, så denne typetil sikkerheten var ekstremt nyttig.

Dokumenter

Fantastisk å se at mitt dumme lille kateksempel nå er en del av de offisielle TypeScript-dokumentene!

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

Svar #3

Det finnes nå en litt lengre dokumentasjon av Record-typen: https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

Siterer det:

Record<Keys, Type> Utgitt: 2.1

Konstruerer en objekttype hvis egenskapsnøkler er Keys og hvis egenskapsverdier er Type. Dette verktøyet kan brukes til å mappe egenskapene til en type til en annen type.

interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};
 
cats.boris;
 
const cats: Record<CatName, CatInfo>

Relaterte artikler