Record<K, V> est un utility type intégré qui décrit un objet dont les clés ont le type K et dont toutes les valeurs ont le type V. Avec des clés string, il décrit un dictionnaire ; avec une union de clés littérales, il décrit un objet qui doit avoir exactement ces clés.
Record n'existe que dans le système de types. À l'exécution, les deux objets sont des objets JavaScript ordinaires : ils fonctionnent avec les littéraux objets, le spread, JSON.stringify et tout ce qui accepte un objet.
Syntaxe et définition
Record<Keys, Value>
Keys doit pouvoir servir de clé d'objet : string, number, symbol, une union de littéraux string ou number, ou un template literal type. Value peut être n'importe quel type. Toute la définition dans la bibliothèque standard de TypeScript tient en une ligne, un mapped type :
type Record<K extends keyof any, T> = {
[P in K]: T;
};
keyof any vaut string | number | symbol, l'ensemble de tous les types de clés possibles. [P in K]: T crée une propriété de type T pour chaque membre de K. Cela explique les deux comportements ci-dessous : un K large comme string produit une signature d'index (n'importe quelle clé), et une union K produit une propriété obligatoire par membre.
Clés union : chaque clé est obligatoire
Quand les clés sont une union de littéraux, un Record doit toutes les lister, et aucune autre. Le compilateur devient alors une checklist :
Ajoutez "cancelled" à Status et les deux objets cessent de compiler tant que vous n'avez pas donné un libellé et une couleur au nouveau statut. Oublier une clé, ou en ajouter une qui n'est pas dans l'union, est une erreur de compilation :
index.ts(4,7): error TS2741: Property 'error' is missing in type '{ idle: string; loading: string; success: string; }' but required in type 'Record<Status, string>'.
index.ts(11,54): error TS2353: Object literal may only specify known properties, and 'paused' does not exist in type 'Record<Status, string>'.
Cela fonctionne aussi avec un enum string comme type de clé : Record<Color, string> exige une entrée par membre de l'enum.
Record<string, T> et la clé absente
Avec des clés string, toute clé est autorisée, et TypeScript type chaque accès comme T, même pour une clé qui n'existe pas. À l'exécution, une clé absente donne undefined :
C'est le bug le plus courant avec Record. Trois façons de le gérer : vérifier avec in ou Object.hasOwn avant de lire, déclarer la valeur comme V | undefined, ou activer l'option de compilation noUncheckedIndexedAccess, qui ajoute | undefined à chaque lecture via une signature d'index dans le projet. Un Record à clés union n'a pas ce problème, car chaque clé est garantie d'exister.
Partial<Record<K, V>> : seulement certaines clés
Pour utiliser une union de clés sans toutes les exiger, enveloppez le Record dans Partial. Les lectures renvoient alors V | undefined, ce qui est honnête :
Une clé mal orthographiée comme jp reste une erreur, ce qui est l'avantage par rapport à Record<string, string>.
Parcourir un Record
Object.keys, Object.values et Object.entries fonctionnent tous. Le piège est le type des clés : Object.keys renvoie string[] et Object.entries renvoie [string, V][], jamais votre union de clés :
TypeScript garde volontairement les clés en string : un objet peut avoir plus de propriétés à l'exécution que son type n'en liste, donc promettre Plan[] serait dangereux dans le cas général. Pour un objet que vous avez créé à partir d'un littéral, comme seats, le cast est sûr.
Construire un Record à partir de données
Les Records sont le résultat habituel d'un regroupement ou d'une indexation de tableau. Partez d'un objet vide typé Record et remplissez-le :
Book["genre"] réutilise l'union de l'interface comme type de clé, donc ajouter un genre à Book oblige byGenre à avoir une nouvelle entrée.
Record<string, unknown> et les interfaces
Record<string, unknown> est un type courant pour « un objet quelconque avec des clés string ». Il accepte les littéraux objets et les valeurs typées avec un alias type, mais une interface est refusée :
index.ts(11,11): error TS2345: Argument of type 'User' is not assignable to parameter of type 'Record<string, unknown>'.
Index signature for type 'string' is missing in type 'User'.
Les interfaces peuvent être étendues par fusion de déclarations, donc TypeScript ne suppose pas qu'elles correspondent à une signature d'index ; les alias de type ne peuvent pas être rouverts, si bien qu'un type User = { name: string } passerait. Les solutions habituelles : accepter object à la place (vous pouvez toujours appeler Object.keys dessus), rendre la fonction générique (<T extends object>(obj: T)), ou déclarer User avec type (la page interface vs type couvre les autres différences).
Record vs signature d'index vs Map
Record<K, V> | { [key: string]: V } | Map<K, V> | |
|---|---|---|---|
| Existe à l'exécution | non, un objet ordinaire | non, un objet ordinaire | oui, une classe |
| Ensemble fixe de clés | oui, avec une union K | non | non |
| Types de clés | string, number, symbol, unions de littéraux, motifs template | string, number, symbol, motifs template | tout, y compris des objets |
| Type d'une clé absente | V (avec des clés string) | V | V | undefined via get |
| Mélange avec des propriétés nommées | par intersection & | oui, dans le même type | non |
| JSON et spread | oui | oui | non, il faut convertir |
| Taille | Object.keys(r).length | Object.keys(o).length | m.size |
| Ajouts et suppressions fréquents | fonctionne | fonctionne | conçue pour ça |
Choisissez Record avec des clés union dès que l'ensemble des clés est connu : c'est la seule option qui vérifie que chaque clé est présente. Pour des clés string ouvertes, Record<string, V> et une signature d'index sont interchangeables, et beaucoup de projets préfèrent Record pour la lisibilité. Passez à une Map quand des clés sont ajoutées et retirées à l'exécution, quand les clés ne sont pas des chaînes, ou quand vous avez besoin de la taille et de l'ordre d'insertion sans effort supplémentaire.
Questions fréquentes
Qu'est-ce que Record en TypeScript ?
Record<K, V> est un utility type intégré qui décrit un objet dont les clés sont de type K et dont toutes les valeurs sont de type V. Record<string, number> est un objet avec n'importe quelles clés string et des valeurs number ; Record<"en" | "de", string> est un objet avec exactement les clés en et de, toutes deux des chaînes.
Quelle est la différence entre Record et Map en TypeScript ?
Record est un type pour un objet JavaScript ordinaire : il fonctionne avec les littéraux objets, le JSON et le spread, et disparaît à la compilation. Map est une classe qui existe à l'exécution, avec get, set, has et size, conserve l'ordre d'insertion pour toutes les clés, accepte n'importe quel type de clé (objets compris), et get renvoie V | undefined. Utilisez un Record pour des données fixes ou issues de JSON, et une Map pour des clés ajoutées et retirées à l'exécution.
Quelle est la différence entre Record<string, T> et { [key: string]: T } ?
Pour les valeurs, c'est le même type : Record<string, T> se développe en un type objet avec une signature d'index string. Deux petites différences : une signature d'index peut porter un nom et côtoyer d'autres propriétés dans le même type, et keyof Record<string, T> vaut string alors que keyof { [key: string]: T } vaut string | number.
Comment parcourir un Record en TypeScript ?
Utilisez Object.entries(record) pour les paires clé-valeur, Object.keys pour les clés et Object.values pour les valeurs. Les clés reviennent en string, pas en K, car un objet peut contenir des clés supplémentaires à l'exécution. Quand le Record a une union de clés connues, faites un cast : (Object.keys(r) as Array<keyof typeof r>).
Comment rendre obligatoires seulement certaines clés d'un Record ?
Avec des clés union, Record<K, V> exige toutes les clés. Enveloppez-le dans Partial pour les rendre toutes optionnelles : Partial<Record<Lang, string>>. Pour un mélange, faites une intersection : Record<"en", string> & Partial<Record<"de" | "fr", string>> exige en et autorise les autres.