Qual è il tipo di record in dattiloscritto?


183

Cosa Record<K, T>significa in dattiloscritto?

Typescript 2.1 ha introdotto il Recordtipo, descrivendolo in un esempio:

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

vedi dattiloscritto 2.1

E la tipi avanzati pagina menziona Recordsotto i Tipi mappati voce al fianco Readonly, Partiale Pick, in quello che sembra essere la sua definizione:

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

Di sola lettura, Parziale e Pick sono omomorfi, mentre Record non lo è. Un indizio che Record non è omomorfo è che non richiede un tipo di input per copiare le proprietà da:

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

E questo è tutto. Oltre alle citazioni di cui sopra, non c'è altra menzione Recordsu typescriptlang.org .

Domande

  1. Qualcuno può dare una semplice definizione di ciò che Recordè?

  2. È Record<K,T>semplicemente un modo di dire "tutte le proprietà su questo oggetto avranno tipo T"? Probabilmente non tutte le proprietà, dal momento che Kha qualche scopo ...

  3. Il Kgenerico proibisce chiavi aggiuntive sull'oggetto che non lo sono Ko le consente e indica semplicemente che le loro proprietà non vengono trasformate T?

  4. Con l'esempio fornito:

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

È esattamente lo stesso di questo ?:

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

6
La risposta a 4. è praticamente "sì", quindi probabilmente dovrebbe rispondere alle tue altre domande.
jcalz,

Risposte:


212
  1. Qualcuno può dare una semplice definizione di ciò che Recordè?

A Record<K, T>è un tipo di oggetto le cui chiavi di proprietà sono Ke i cui valori di proprietà sono T. Cioè, keyof Record<K, T>è equivalente a Ked Record<K, T>[K]è (sostanzialmente) equivalente a T.

  1. È Record<K,T>semplicemente un modo di dire "tutte le proprietà su questo oggetto avranno tipo T"? Probabilmente non tutti gli oggetti, dal momento che Kha qualche scopo ...

Come notate, Kha uno scopo ... limitare le chiavi di proprietà a valori particolari. Se vuoi accettare tutte le possibili chiavi con valori di stringa, potresti fare qualcosa di simile Record<string, T>, ma il modo idiomatico di farlo è quello di utilizzare una firma indice come { [k: string]: T }.

  1. Il Kgenerico proibisce chiavi aggiuntive sull'oggetto che non lo sono Ko le consente e indica semplicemente che le loro proprietà non vengono trasformate T?

Non "proibisce" esattamente le chiavi aggiuntive: dopo tutto, a un valore è generalmente consentito di avere proprietà non esplicitamente menzionate nel suo tipo ... ma non riconoscerebbe l'esistenza di tali proprietà:

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

e li tratterebbe come proprietà in eccesso che a volte vengono rifiutate:

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

e talvolta accettato:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Con l'esempio fornito:

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

    È esattamente lo stesso di questo ?:

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

Sì!

Spero che aiuti. In bocca al lupo!


1
Molto appreso e una domanda sul perché "il modo idiomatico di farlo è quello di usare una firma indice" non una Record? Non trovo alcuna informazione correlata su questo "modo idiomatico".
Legend80s

2
Puoi usare Record<string, V>per significare {[x: string]: V}se vuoi; Probabilmente l'ho fatto anche io. La versione della firma dell'indice è più diretta: sono dello stesso tipo, ma la prima è un alias di tipo di un tipo mappato che restituisce una firma di indice, mentre la seconda è solo la firma di indice direttamente. A parità di tutti gli altri, consiglierei quest'ultimo. Allo stesso modo non userei Record<"a", string>al posto di {a: string}se non ci fosse qualche altra ragione contestuale convincente a farlo.
jcalz,

1
" A parità di tutti gli altri, consiglierei quest'ultimo. " Perché? Il mio io pre-dattiloscritto è d'accordo, ma so che il primo sarà più o meno auto-commentante per le persone che provengono dal lato C #, per esempio, e non peggio per JavaScript-to-Typescripters. Sei solo interessato a saltare il passaggio di transpilazione per quei costrutti?
ruffin,

1
Solo la mia opinione: il comportamento di Record<string, V>ha senso solo se sai già come funzionano le firme di indice in TypeScript. Ad esempio, data x: Record<string, string>, x.foosarà a quanto pare essere una stringin fase di compilazione, ma in realtà è probabile che sia string | undefined. Questa è una lacuna nel modo in cui --strictNullChecksfunziona (vedi # 13778 ). Preferisco avere i nuovi arrivati si occupano {[x: string]: V}direttamente invece di aspettarsi loro di seguire la catena da Record<string, V>tramite {[P in string]: V}al comportamento firma indice.
jcalz,

Volevo sottolineare che logicamente un tipo può essere definito come un insieme di tutti i valori contenuti nel tipo. data questa interpretazione, penso che Record <stringa, V> sia ragionevole come un'astrazione per semplificare il codice invece di stabilire tutti i possibili valori. È simile all'esempio nella documentazione relativa ai tipi di utilità: Record <T, V> dove type T = 'a' | 'b' | 'c'. Mai fare Record <'a', stringa> non è un buon esempio di contatore, perché non segue lo stesso schema. Inoltre non aggiunge per riutilizzare o semplificare il codice attraverso l'astrazione come fanno gli altri esempi.
Scott Leonard,

69

Un record consente di creare un nuovo tipo da un'Unione. I valori nell'Unione sono usati come attributi del nuovo tipo.

Ad esempio, supponiamo che io abbia un'Unione come questa:

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

Ora voglio creare un oggetto che contenga informazioni su tutti i gatti, posso creare un nuovo tipo usando i valori nell'Unione CatName come chiavi.

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

Se voglio soddisfare questa CatList, devo creare un oggetto come questo:

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

Ottieni una sicurezza di tipo molto forte:

  • Se dimentico un gatto, ricevo un errore.
  • Se aggiungo un gatto non consentito, viene visualizzato un errore.
  • Se in seguito cambio CatNames, ricevo un errore. Ciò è particolarmente utile perché CatNames è probabilmente importato da un altro file e probabilmente utilizzato in molti luoghi.

Esempio di React nel mondo reale.

L'ho usato di recente per creare un componente Status. Il componente riceve un oggetto di stato e quindi visualizza un'icona. Ho semplificato molto il codice qui a scopo illustrativo

Ho avuto un sindacato come questo:

type Statuses = "failed" | "complete";

L'ho usato per creare un oggetto come questo:

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

Potrei quindi renderlo destrutturando un elemento dall'oggetto in oggetti di scena, in questo modo:

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

Se il sindacato Statuses verrà successivamente esteso o modificato, so che il mio componente Status non verrà compilato e verrà visualizzato un errore che posso risolvere immediatamente. Ciò mi consente di aggiungere ulteriori stati di errore all'app.

Si noti che l'app effettiva aveva dozzine di stati di errore a cui era stato fatto riferimento in più punti, quindi questo tipo di sicurezza era estremamente utile.


Presumo che la maggior parte delle volte type Statusesviva in caratteri NON definiti da te? Altrimenti vedo qualcosa come un'interfaccia con un enum che si adatta meglio, giusto?
Victorio Berra,

Ciao @victorio, non sono sicuro di come un enum risolverebbe il problema, non si ottiene un errore in un enum se si perde una chiave. È solo una mappatura tra chiavi e valori.
superluminario,

1
Capisco cosa intendi adesso. Venendo da C # non abbiamo modi intelligenti per farlo. La cosa più vicina sarebbe un dizionario di Dictionary<enum, additional_metadata>. Il tipo Record è un ottimo modo per rappresentare quel modello enum + metadata.
Victorio Berra,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.