Menu

Dictionnaire en TypeScript : index signature, Record et Map

TypeScript n'a pas de classe dictionnaire ou hashmap : on type une table clé valeur avec une index signature, Record<K, V> ou Map<K, V>. Découvrez chacune de ces solutions, comment vérifier qu'une clé existe, ajouter, supprimer et parcourir, et pourquoi noUncheckedIndexedAccess compte pour les dictionnaires.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

TypeScript n'a pas de classe dictionnaire ou hashmap à part. Un dictionnaire est soit un objet simple typé avec une index signature, { [key: string]: number }, soit le même type écrit Record<string, number>, soit un Map<string, number>. Les trois stockent des valeurs par clé ; ils diffèrent par les types de clés acceptés, par la façon dont les clés absentes sont typées et par leur sérialisation.

Pour des clés de type chaîne et des données au format JSON, un objet avec Record<string, T> est le choix habituel. Pour des clés qui ne sont pas des chaînes, ou des entrées ajoutées et retirées en permanence, utilisez un Map.

Index signatures

Une index signature, [key: KeyType]: ValueType, signifie « toute clé de ce type correspond à une valeur de ce type ». Le nom de la clé (key, name, userId) ne sert que de documentation. Le type de clé peut être string, number, symbol, un motif template literal, ou une union de ces types.

JavaScript convertit les clés numériques en chaînes, donc un objet { [id: number]: string } a quand même des clés de type chaîne à l'exécution : Object.keys({ 1: "one" }) donne [ '1' ]. L'index signature numérique ne fait que restreindre la façon dont vous pouvez l'indexer en TypeScript.

Record<K, V>

Record<string, V> est un raccourci pour { [key: string]: V }. Avec une union de clés littérales à la place de string, il devient un dictionnaire fermé qui doit contenir chaque clé :

Oublier staging dans urls provoque l'erreur de compilation TS2741 (Property 'staging' is missing...), ce qui fait de Record avec des clés en union une table de correspondance vérifiée. Partial rend chaque valeur number | undefined.

Map comme table de hachage

Un Map accepte des clés de n'importe quel type, conserve l'ordre d'insertion, possède une propriété size et type honnêtement les clés absentes : get renvoie V | undefined.

Vérifier qu'une clé existe

Il existe plusieurs tests, et ils ne veulent pas tous dire la même chose :

TestFonctionne surAttention à
Object.hasOwn(obj, key)objetsES2022 ; utilisez Object.prototype.hasOwnProperty.call(obj, key) sur les cibles plus anciennes
key in objobjetsVrai aussi pour les clés héritées comme toString et constructor
obj[key] !== undefinedobjetsNe distingue pas une clé absente d'une clé qui contient undefined
if (obj[key])objetsFaux aussi pour les valeurs 0, "" et false
map.has(key)MapNe restreint pas le type d'un map.get(key) qui suit
map.get(key) !== undefinedMapMême réserve sur undefined que pour les objets

Ce problème de clés héritées explique pourquoi des clés fournies par les utilisateurs, comme "constructor" ou "__proto__", rendent les objets simples risqués comme dictionnaires. Un Map n'a pas de telles clés.

Le problème du type des clés absentes

Sur une index signature ou un Record<string, T>, lire n'importe quelle clé donne le type T, même pour une clé qui n'existe pas. Le compilateur vous laisse appeler des méthodes sur une valeur qui vaut undefined à l'exécution :

L'option de compilation noUncheckedIndexedAccess corrige cela : avec elle, colors["grass"] a le type string | undefined et l'appel à toUpperCase est une erreur de compilation tant que vous ne l'avez pas vérifié. Elle ne fait pas partie de strict et doit donc être activée à part dans tsconfig.json ; voir le mode strict pour les autres options qui l'accompagnent. Un Map n'a pas ce trou, puisque get inclut toujours undefined.

Ajouter, supprimer et parcourir

delete fonctionne sur les propriétés d'une index signature. Sur une propriété nommée obligatoire d'un type objet, c'est l'erreur de compilation TS2790, The operand of a 'delete' operator must be optional.

Laquelle choisir

BesoinUtiliser
Clés de type chaîne, JSON en entrée ou en sortieRecord<string, T>
Un ensemble de clés fixe et connu, toutes obligatoiresRecord<"a" | "b", T>
Des propriétés nommées plus des clés libresun type objet avec une index signature
Des clés qui sont des objets, des nombres gardés comme nombres, ou tout ce qui n'est pas une chaîneMap<K, V>
Des ajouts et suppressions fréquents, ou une taille lue souventMap<K, V>
Des clés qui viennent des utilisateursMap<K, V> (pas de clés héritées)

Questions fréquentes

Comment créer un dictionnaire en TypeScript ?

Typez un objet simple avec une index signature, const ages: { [name: string]: number } = {}, ou avec l'équivalent Record<string, number>. Ajoutez ensuite des entrées avec ages["ada"] = 36. Pour des clés qui ne sont pas des chaînes, ou une collection avec beaucoup d'ajouts et de suppressions, utilisez new Map<string, number>().

TypeScript a-t-il une HashMap ?

Pas sous ce nom. Le Map intégré de JavaScript est une table de hachage : Map<K, V> stocke des paires clé valeur avec une recherche rapide par clé, conserve l'ordre d'insertion et accepte n'importe quel type de clé. Un objet simple typé Record<string, V> est l'autre choix courant pour des clés de type chaîne.

Comment vérifier qu'une clé existe dans un dictionnaire TypeScript ?

Pour un dictionnaire objet, utilisez Object.hasOwn(dict, key) ou key in dict (qui voit aussi les propriétés héritées comme toString), ou lisez la valeur et comparez-la à undefined. Pour un Map, utilisez map.has(key), ou testez directement le résultat de map.get(key), car has ne restreint pas le type d'un get qui suit.

Quelle est la différence entre une index signature et Record ?

{ [key: string]: T } et Record<string, T> décrivent le même type. Record est plus court et accepte aussi une union de clés précises, Record<"a" | "b", T>, qui exige chaque clé. Une index signature peut être combinée à des propriétés nommées dans un même type objet, et sa clé peut porter un nom qui la documente, comme dans { [userId: string]: User }.

Pourquoi lire une clé absente d'un dictionnaire ne donne-t-il pas d'erreur ?

Par défaut, dict[key] sur une index signature ou un Record<string, T> a le type T, alors qu'à l'exécution la valeur vaut undefined pour une clé absente. Activez noUncheckedIndexedAccess dans tsconfig.json et le type devient T | undefined, ce qui oblige à vérifier. strict n'inclut pas cette option.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER