Quel est le type d'enregistrement dans le tapuscrit?


183

Que Record<K, T>signifie dans Typescript?

Typescript 2.1 a introduit le Recordtype, en le décrivant dans un exemple:

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

voir Typescript 2.1

Et les types avancés la page mentionne Recordsous les types cartographiées rubrique à côté Readonly, Partialet Pick, dans ce qui semble être sa définition:

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

Readonly, Partial et Pick sont homomorphes alors que Record ne l'est pas. Un indice que Record n'est pas homomorphe est qu'il ne prend pas de type d'entrée pour copier les propriétés:

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

Et c'est tout. Outre les citations ci-dessus, il n'y a aucune autre mention de Recordsur typescriptlang.org .

Des questions

  1. Quelqu'un peut-il donner une définition simple de ce que Recordc'est?

  2. Est-ce Record<K,T>simplement une façon de dire "toutes les propriétés de cet objet auront un type T"? Probablement pas toutes les propriétés, car elles Kont un but ...

  3. Le Kgénérique interdit-il les clés supplémentaires sur l'objet qui ne le sont pas K, ou les autorise-t-il et indique-t-il simplement que leurs propriétés ne sont pas transformées en T?

  4. Avec l'exemple donné:

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

Est-ce exactement la même chose?:

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

6
La réponse à 4. est à peu près «oui», donc cela devrait probablement répondre à vos autres questions.
jcalz

Réponses:


212
  1. Quelqu'un peut-il donner une définition simple de ce que Recordc'est?

A Record<K, T>est un type d'objet dont les clés de propriété sont Ket dont les valeurs de propriété sont T. Autrement dit, keyof Record<K, T>équivaut à Ket Record<K, T>[K]équivaut (fondamentalement) à T.

  1. Est-ce Record<K,T>simplement une façon de dire "toutes les propriétés de cet objet auront un type T"? Probablement pas tous les objets, car ils Kont un but ...

Comme vous le notez, Ka un but ... de limiter les clés de propriété à des valeurs particulières. Si vous voulez accepter toutes les clés à valeur de chaîne possibles, vous pouvez faire quelque chose comme Record<string, T>, mais la manière idiomatique de le faire est d'utiliser une signature d'index comme { [k: string]: T }.

  1. Le Kgénérique interdit-il les clés supplémentaires sur l'objet qui ne le sont pas K, ou les autorise-t-il et indique-t-il simplement que leurs propriétés ne sont pas transformées en T?

Cela n'interdit pas exactement les clés supplémentaires: après tout, une valeur est généralement autorisée à avoir des propriétés non explicitement mentionnées dans son type ... mais elle ne reconnaîtrait pas que de telles propriétés existent:

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

et il les traiterait comme des propriétés excédentaires qui sont parfois rejetées:

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

et parfois accepté:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Avec l'exemple donné:

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

    Est-ce exactement la même chose?:

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

Oui!

J'espère que cela pourra aider. Bonne chance!


1
Très appris et une question pourquoi "la manière idiomatique de faire cela est d'utiliser une signature d'index" et non une signature Record? Je ne trouve aucune information relative à cette "manière idiomatique".
legend80s

2
Vous pouvez utiliser Record<string, V>pour signifier {[x: string]: V}si vous le souhaitez; J'ai probablement même fait ça moi-même. La version de la signature d'index est plus directe: ils sont du même type, mais le premier est un alias de type d'un type mappé qui s'évalue en une signature d'index, tandis que le second n'est que la signature d'index directement. Toutes choses étant égales par ailleurs, je recommanderais ce dernier. De même, je n'utiliserais pas Record<"a", string>à la place de à {a: string}moins qu'il n'y ait une autre raison contextuelle convaincante.
jcalz

1
" Toutes choses étant égales par ailleurs, je recommanderais ce dernier. " Pourquoi? Mon auto pré-Typescript est d'accord, mais je sais que le premier sera plus, euh, auto-commentant pour les personnes venant du côté C #, par exemple, et pas pire pour les JavaScript-to-Typescripters. Êtes-vous simplement intéressé à sauter l'étape de transpilation pour ces constructions?
ruffin

1
Juste mon avis: le comportement de Record<string, V>n'a de sens que si vous savez déjà comment les signatures d'index fonctionnent dans TypeScript. Par exemple, étant donné x: Record<string, string>, x.foosera apparemment un stringau moment de la compilation, mais en réalité, il est susceptible de l'être string | undefined. C'est une lacune dans la façon dont --strictNullChecksfonctionne (voir # 13778 ). Je préfère avoir les nouveaux arrivants traitent {[x: string]: V}directement au lieu de les attendre à suivre la chaîne de Record<string, V>grâce {[P in string]: V}au comportement de la signature de l' indice.
jcalz

Je voulais souligner que logiquement, un type peut être défini comme un ensemble de toutes les valeurs contenues dans le type. étant donné cette interprétation, je pense que Record <string, V> est raisonnable en tant qu'abstraction pour simplifier le code au lieu de présenter toutes les valeurs possibles. Il est similaire à l'exemple de la documentation des types d'utilitaires: Record <T, V> où type T = 'a' | «b» | «c». Ne jamais faire Record <'a', string> n'est pas un bon exemple de compteur, car il ne suit pas le même modèle. Cela n'ajoute pas non plus à la réutilisation ou à la simplification du code par l'abstraction comme le font les autres exemples.
Scott Leonard

69

Un enregistrement vous permet de créer un nouveau type à partir d'une union. Les valeurs de l'Union sont utilisées comme attributs du nouveau type.

Par exemple, disons que j'ai un syndicat comme celui-ci:

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

Maintenant, je veux créer un objet qui contient des informations sur tous les chats, je peux créer un nouveau type en utilisant les valeurs de CatName Union comme clés.

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

Si je veux satisfaire cette CatList, je dois créer un objet comme celui-ci:

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

Vous obtenez une sécurité de type très forte:

  • Si j'oublie un chat, j'obtiens une erreur.
  • Si j'ajoute un chat qui n'est pas autorisé, j'obtiens une erreur.
  • Si je change plus tard CatNames, j'obtiens une erreur. Ceci est particulièrement utile car CatNames est probablement importé d'un autre fichier, et probablement utilisé dans de nombreux endroits.

Exemple de React dans le monde réel.

Je l'ai utilisé récemment pour créer un composant Status. Le composant recevrait un accessoire d'état, puis rendrait une icône. J'ai beaucoup simplifié le code ici à des fins d'illustration

J'avais un syndicat comme celui-ci:

type Statuses = "failed" | "complete";

J'ai utilisé ceci pour créer un objet comme celui-ci:

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

Je pourrais ensuite rendre en déstructurant un élément de l'objet en accessoires, comme ceci:

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

Si l'union Statuses est ultérieurement étendue ou modifiée, je sais que mon composant Status échouera à se compiler et j'obtiendrai une erreur que je peux corriger immédiatement. Cela me permet d'ajouter des états d'erreur supplémentaires à l'application.

Notez que l'application réelle contenait des dizaines d'états d'erreur référencés à plusieurs endroits, ce type de sécurité était donc extrêmement utile.


Je suppose que la plupart du temps type Statusesvit dans des typages NON définis par vous? Sinon, je peux voir quelque chose comme une interface avec une énumération qui convient mieux, non?
Victorio Berra

Salut @victorio, je ne sais pas comment une énumération résoudrait le problème, vous n'obtenez pas d'erreur dans une énumération si vous manquez une clé. C'est juste un mappage entre les clés et les valeurs.
superluminaire

1
Je vois ce que tu veux dire maintenant. Venant de C #, nous n'avons pas de moyens intelligents de le faire. La chose la plus proche serait un dictionnaire de Dictionary<enum, additional_metadata>. Le type d'enregistrement est un excellent moyen de représenter ce modèle enum + metadata.
Victorio Berra
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.