Menu

TypeScript Dictionary: Index-Signaturen, Record und Map

TypeScript hat keine Dictionary- oder Hashmap-Klasse; ein Lookup mit Schlüsseln und Werten typisierst du mit einer Index-Signatur, Record<K, V> oder Map<K, V>. Jede Variante erklärt, wie du prüfst, ob ein Schlüssel existiert, hinzufügst, löschst und iterierst, und warum noUncheckedIndexedAccess für Dictionaries wichtig ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

TypeScript hat keine eigene Dictionary- oder Hashmap-Klasse. Ein Dictionary ist entweder ein einfaches Objekt mit Index-Signatur, { [key: string]: number }, derselbe Typ als Record<string, number> geschrieben, oder eine Map<string, number>. Alle drei speichern Werte unter Schlüsseln; sie unterscheiden sich bei den Schlüsseltypen, der Typisierung fehlender Schlüssel und der Serialisierung.

Für String-Schlüssel und Daten in JSON-Form ist ein Objekt mit Record<string, T> die übliche Wahl. Für Schlüssel, die keine Strings sind, oder Einträge, die ständig hinzukommen und wegfallen, nimm eine Map.

Index-Signaturen

Eine Index-Signatur, [key: KeyType]: ValueType, sagt: „Jeder Schlüssel dieses Typs verweist auf einen Wert dieses Typs.“ Der Schlüsselname (key, name, userId) ist nur Dokumentation. Schlüsseltypen können string, number, symbol, Template-Literal-Muster oder Unions davon sein.

JavaScript wandelt Zahlenschlüssel in Strings um, also hat ein Objekt vom Typ { [id: number]: string } zur Laufzeit trotzdem String-Schlüssel: Object.keys({ 1: "one" }) ist [ '1' ]. Die Index-Signatur mit Zahl schränkt nur ein, wie du in TypeScript darauf zugreifen darfst.

Record<K, V>

Record<string, V> ist die Kurzform von { [key: string]: V }. Mit einer Union aus Literal-Schlüsseln statt string wird es zu einem festen Dictionary, das jeden Schlüssel enthalten muss:

staging in urls wegzulassen ist der Compilerfehler TS2741 (Property 'staging' is missing...), was Record mit Union-Schlüsseln zu einer geprüften Lookup-Tabelle macht. Partial macht jeden Wert zu number | undefined.

Map als Hashmap

Eine Map akzeptiert Schlüssel jedes Typs, behält die Einfügereihenfolge, hat eine size und typisiert fehlende Schlüssel ehrlich: get gibt V | undefined zurück.

Prüfen, ob ein Schlüssel existiert

Es gibt mehrere Prüfungen, und sie bedeuten nicht alle dasselbe:

PrüfungFunktioniert beiAchtung
Object.hasOwn(obj, key)ObjektenES2022; bei älteren Targets Object.prototype.hasOwnProperty.call(obj, key) verwenden
key in objObjektenAuch true für geerbte Schlüssel wie toString und constructor
obj[key] !== undefinedObjektenUnterscheidet einen fehlenden Schlüssel nicht von einem, der als undefined gespeichert ist
if (obj[key])ObjektenAuch false bei den Werten 0, "" und false
map.has(key)MapEngt ein späteres map.get(key) nicht ein
map.get(key) !== undefinedMapDerselbe Vorbehalt zu undefined wie bei Objekten

Das Problem mit geerbten Schlüsseln ist der Grund, warum Schlüssel aus Benutzereingaben wie "constructor" oder "__proto__" einfache Objekte als Dictionaries riskant machen. Eine Map hat solche Schlüssel nicht.

Das Typproblem bei fehlenden Schlüsseln

Bei einer Index-Signatur oder Record<string, T> hat das Lesen jedes Schlüssels den Typ T, auch eines Schlüssels, der nicht existiert. Der Compiler lässt dich Methoden auf einem Wert aufrufen, der zur Laufzeit undefined ist:

Die Compileroption noUncheckedIndexedAccess behebt das: Mit ihr hat colors["grass"] den Typ string | undefined, und der Aufruf von toUpperCase ist ein Compilerfehler, bis du prüfst. Sie gehört nicht zu strict und muss daher in tsconfig.json eigens eingeschaltet werden; die übrigen Flags daneben stehen unter Strict Mode. Eine Map hat diese Lücke nicht, da get immer undefined einschließt.

Hinzufügen, Entfernen und Iterieren

delete funktioniert bei Eigenschaften aus einer Index-Signatur. Bei einer benannten Pflichteigenschaft eines Objekttyps ist es der Compilerfehler TS2790, The operand of a 'delete' operator must be optional.

Welche Variante wofür

BedarfVerwenden
String-Schlüssel, JSON rein oder rausRecord<string, T>
Eine feste, bekannte Menge von Schlüsseln, alle PflichtRecord<"a" | "b", T>
Benannte Eigenschaften plus beliebige weitere Schlüsselein Objekttyp mit Index-Signatur
Schlüssel, die Objekte sind, Zahlen, die Zahlen bleiben sollen, oder andere Nicht-StringsMap<K, V>
Häufiges Hinzufügen und Löschen oder oft gelesene GrößeMap<K, V>
Schlüssel, die von Benutzern kommenMap<K, V> (keine geerbten Schlüssel)

Häufig gestellte Fragen

Wie erstelle ich ein Dictionary in TypeScript?

Typisiere ein einfaches Objekt mit einer Index-Signatur, const ages: { [name: string]: number } = {}, oder dem gleichwertigen Record<string, number>. Dann fügst du Einträge mit ages["ada"] = 36 hinzu. Für Schlüssel, die keine Strings sind, oder eine Sammlung mit häufigem Hinzufügen und Löschen nimmst du new Map<string, number>().

Hat TypeScript eine HashMap?

Nicht unter diesem Namen. Die eingebaute Map von JavaScript ist eine Hashmap: Map<K, V> speichert Schlüssel-Wert-Paare mit schnellem Zugriff über den Schlüssel, behält die Einfügereihenfolge und akzeptiert jeden Schlüsseltyp. Ein einfaches Objekt vom Typ Record<string, V> ist die andere gängige Wahl für String-Schlüssel.

Wie prüfe ich, ob ein Schlüssel in einem TypeScript-Dictionary existiert?

Bei einem Objekt-Dictionary nimmst du Object.hasOwn(dict, key) oder key in dict (das auch geerbte Eigenschaften wie toString sieht), oder du liest den Wert und vergleichst ihn mit undefined. Bei einer Map nimmst du map.has(key) oder prüfst das Ergebnis von map.get(key) direkt, da has ein späteres get nicht einengt.

Was ist der Unterschied zwischen einer Index-Signatur und Record?

{ [key: string]: T } und Record<string, T> beschreiben denselben Typ. Record ist kürzer und kann auch eine Union konkreter Schlüssel nehmen, Record<"a" | "b", T>, was jeden Schlüssel verlangt. Eine Index-Signatur lässt sich in einem Objekttyp mit benannten Eigenschaften kombinieren, und ihr Schlüssel kann einen dokumentierenden Namen tragen, etwa { [userId: string]: User }.

Warum gibt das Lesen eines fehlenden Schlüssels keinen Fehler?

Standardmäßig hat dict[key] bei einer Index-Signatur oder Record<string, T> den Typ T, obwohl der Wert für einen fehlenden Schlüssel zur Laufzeit undefined ist. Aktiviere noUncheckedIndexedAccess in tsconfig.json, dann wird der Typ zu T | undefined und erzwingt eine Prüfung. strict enthält diese Option nicht.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S