typescript - Ce este tipul Record?


Ce înseamnă Record<K, T> în Typescript?

Typescript 2.1 a introdus tipul Record, descriindu-l într-un exemplu:

// 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>

vezi Typescript 2.1

Iar pagina Despre Tipuri Avansate menționează Record sub titlul Tipuri Mapate împreună cu Readonly, Partial și Pick, în ceea ce pare a fi definiția sa:

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

Readonly, Partial și Pick sunt omomorfice, în timp ce Record nu este. Un indiciu că Record nu este omomorfic este că nu ia un tip de intrare pentru a copia proprietăți:

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

Și asta este. În afară de citatele de mai sus, nu există nicio altă mențiune a Record pe typescriptlang.org.

Întrebări

  1. Poate cineva să dea o definiție simplă a ceea ce este Record?

  2. Record<K,T> este pur și simplu un mod de a spune „toate proprietățile acestui obiect vor avea tipul T”? Probabil nu toate proprietățile, deoarece K are un anumit scop...

  3. Interzice genericul K cheile suplimentare pe obiect care nu sunt K, sau le permite și doar indică faptul că proprietățile lor nu sunt transformate în T?

  4. Cu exemplul dat:

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

    Este exact la fel ca acesta?

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

Răspuns #1

  1. Poate cineva să dea o definiție simplă a ceea ce este Record?

Un Record<K, T> este un tip de obiect ale cărui chei de proprietate sunt K și ale căror valori de proprietate sunt T. Adică, keyof Record<K, T> este echivalent cu K, iar Record<K, T>[K] este (practic) echivalent cu T.

  1. Este Record<K,T> doar o modalitate de a spune „toate proprietățile acestui obiect vor avea tipul T”? Probabil nu toate obiectele, deoarece K are un anumit scop...

După cum observați, K are un scop... de a limita cheile proprietăților la valori anumite. Dacă doriți să acceptați toate cheile posibile cu valori de tip șir, ați putea face ceva de genul Record<string, T>, dar modul idiomatic de a face acest lucru este să folosiți o semnătură de index precum { [k: string]: T }.

  1. Interzice genericul K cheile suplimentare pe obiect care nu sunt K, sau le permite și doar indică faptul că proprietățile lor nu sunt transformate în T?

Nu „interzice” exact chei suplimentare: la urma urmei, într-o valoare este în general permis să existe proprietăți care nu sunt menționate explicit în tipul său... dar nu ar recunoaște că astfel de proprietăți există:

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

și le-ar trata ca proprietăți excedentare care sunt uneori respinse:

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

și uneori acceptat:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Cu exemplul dat:

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

    Este exact la fel ca acesta?

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

Da!

Răspunsul #2

Un Record îți permite să creezi un nou tip dintr-un Union. Valorile din Union sunt folosite ca atribute ale noului tip.

De exemplu, să spunem că am o Uniune astfel:

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

Acum vreau să creez un obiect care conține informații despre toate pisicile, pot crea un nou tip folosind valorile din uniunea CatNames ca chei.

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

Dacă vreau să satisfac acest CatList, trebuie să creez un obiect astfel:

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

Obții o siguranță de tip foarte puternică:

  • Dacă uit o pisică, primesc o eroare.
  • Dacă adaug o pisică care nu este permisă, primesc o eroare.
  • Dacă mai târziu schimb CatNames, primesc o eroare. Acest lucru este deosebit de util deoarece CatNames este probabil importat dintr-un alt fișier și este probabil folosit în multe locuri.

Exemplu real de React.

Am folosit acest lucru recent pentru a crea un component Status. Componenta ar primi un prop status și apoi ar afișa o pictogramă. Am simplificat foarte mult codul aici în scop ilustrativ

Am avut o uniune de genul acesta:

type Statuses = "failed" | "complete";

Am folosit asta pentru a crea un obiect ca acesta:

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

Aș putea apoi să redau prin destructurarea unui element din obiect în props, astfel:

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

Dacă uniunea Statuses este extinsă sau modificată ulterior, știu că componenta mea Status nu se va compila și voi primi o eroare pe care o pot remedia imediat. Acest lucru îmi permite să adaug stări suplimentare de eroare în aplicație.

Rețineți că aplicația reală avea zeci de stări de eroare care erau menționate în mai multe locuri, așa că această siguranță a tipurilor a fost extrem de utilă.

Documente

Minunat să văd că mica mea și neînsemnata exemplu cu pisica face acum parte din documentația oficială TypeScript!

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

Răspunsul #3

Există acum o documentație puțin mai lungă despre tipul Record: https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

Citat că:

Record<Chei, Tip> Lansat: 2.1

Construiește un tip de obiect ale cărui chei de proprietate sunt Keys și ale căror valori de proprietate sunt Type. Acest utilitar poate fi folosit pentru a mapa proprietățile unui tip către un alt tip.

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>