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üfung | Funktioniert bei | Achtung |
|---|---|---|
Object.hasOwn(obj, key) | Objekten | ES2022; bei älteren Targets Object.prototype.hasOwnProperty.call(obj, key) verwenden |
key in obj | Objekten | Auch true für geerbte Schlüssel wie toString und constructor |
obj[key] !== undefined | Objekten | Unterscheidet einen fehlenden Schlüssel nicht von einem, der als undefined gespeichert ist |
if (obj[key]) | Objekten | Auch false bei den Werten 0, "" und false |
map.has(key) | Map | Engt ein späteres map.get(key) nicht ein |
map.get(key) !== undefined | Map | Derselbe 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
| Bedarf | Verwenden |
|---|---|
| String-Schlüssel, JSON rein oder raus | Record<string, T> |
| Eine feste, bekannte Menge von Schlüsseln, alle Pflicht | Record<"a" | "b", T> |
| Benannte Eigenschaften plus beliebige weitere Schlüssel | ein Objekttyp mit Index-Signatur |
| Schlüssel, die Objekte sind, Zahlen, die Zahlen bleiben sollen, oder andere Nicht-Strings | Map<K, V> |
| Häufiges Hinzufügen und Löschen oder oft gelesene Größe | Map<K, V> |
| Schlüssel, die von Benutzern kommen | Map<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.