TypeScript Map: Eine typisierte Key-Value-Collection
TypeScript Map
TypeScript Map: Typisieren, Durchlaufen und Speichern | We.Inc
Typisiere eine Map<K, V> in TypeScript, durchlaufe sie, wandle sie in JSON und zurück und entscheide zwischen Map, Record und einem einfachen Objekt. Probier eine Live-Map im Browser aus.
In TypeScript ist eine Map die ganz normale JavaScript-Map mit Typen für Schlüssel und Werte: new Map<string, number>(). Schlüssel können beliebige Werte sein, Einträge kommen in der Reihenfolge zurück, in der sie zuerst hinzugefügt wurden, size zählt sie, und get gibt V | undefined zurück. JSON.stringify serialisiert keine Map, wandle sie also vorher um.
Map-Methoden und was TypeScript zurückgibt
Aufruf
Macht
Typ, den du bekommst (Map<string, number>)
map.set(k, v)
Fügt einen Eintrag hinzu oder aktualisiert ihn
Map<string, number> (verkettbar)
map.get(k)
Liest einen Wert
number | undefined
map.has(k)
Prüft einen Schlüssel
map.delete(k)
Entfernt einen Eintrag
boolean (true, wenn er existierte)
Zählt die Einträge
for (const [k, v] of map)
Durchläuft in Einfügereihenfolge
k: string, v: number
Der Playground unten ist eine echte Map, die in deinem Browser läuft. Füge Einträge hinzu, aktualisiere und lösche sie und achte auf drei Dinge: die Reihenfolge, die Einträge behalten, was size meldet, und den Unterschied zwischen dem Stringifizieren der Map selbst und dem einer umgewandelten Kopie.
Gib einen Schlüssel und eine Zahl ein und drücke Setzen, um diesen Eintrag hinzuzufügen oder zu aktualisieren.
Drücke Löschen, um den Schlüssel im Schlüsselfeld zu entfernen, oder Leeren, um die Map zu leeren.
Lies die Liste der Einträge, sie zeigt die eigene Iterationsreihenfolge der Map.
Vergleiche die beiden JSON-Zeilen, um zu sehen, warum eine Map umgewandelt werden muss, bevor du sie speicherst oder sendest.
Gib beim Erstellen einer leeren Map die generischen Argumente an. new Map() allein ist Map<any, any>, und jeder spätere Lesezugriff verliert seinen Typ.
Prüfe mit has() oder vergleiche get() mit undefined, bevor du einen Wert nutzt. Eine Non-null-Assertion verdeckt den Fall, dass der Schlüssel wirklich fehlt.
Objektschlüssel werden per Referenz verglichen. Zwei Objekte mit denselben Feldern sind zwei verschiedene Schlüssel, nimm also einen id-String, wenn du denselben Datensatz meinst.
Um eine Map in localStorage aufzubewahren, speicherst du JSON.stringify([...map]) und baust sie mit new Map(JSON.parse(text)) wieder auf. Das Array aus Paaren behält Nicht-String-Schlüssel und die Reihenfolge.
Häufige Fragen
Wie deklariere ich den Typ einer Map in TypeScript?
Übergib Schlüssel- und Werttyp als generische Argumente: const prices = new Map<string, number>(). Für ein Feld oder einen Parameter schreibst du den Typ als Map<string, number>, oder als ReadonlyMap<string, number>, wenn die Funktion nur lesen soll.
Warum enthält der Typ von map.get() undefined?
Weil der Compiler nicht wissen kann, welche Schlüssel zu einem bestimmten Zeitpunkt in der Map stehen. get ist als V | undefined typisiert, also grenzt du mit einer if-Prüfung, einem ??-Fallback oder einem has()-Test ein, bevor du den Wert als V behandelst.
Warum ergibt JSON.stringify(map) {}?
Eine Map hält ihre Einträge in einem internen Slot, nicht als Objekteigenschaften, daher sieht JSON ein leeres Objekt. Wandle zuerst um: Object.fromEntries(map) für String-Schlüssel oder [...map] für ein Array aus Paaren, das jeden Schlüsseltyp übersteht.
Sollte ich eine Map oder einen Record verwenden?
Nimm einen Record für eine feste Menge benannter Schlüssel und für Daten, die von und zu JSON wandern. Nimm eine Map, wenn Einträge während der Laufzeit der App kommen und gehen, wenn Schlüssel Zahlen oder Objekte sind oder wenn die Einfügereihenfolge wichtig ist.
Wandert ein Schlüssel beim Aktualisieren ans Ende?
Nein. Einen vorhandenen Schlüssel zu setzen ändert seinen Wert, behält aber seine ursprüngliche Position. Um ihn ans Ende zu verschieben, löschst du den Schlüssel und setzt ihn erneut.
Map, Record und ein einfaches Objekt
Ein einfaches Objekt erbt von Object.prototype, Namen wie constructor sind also schon vorhanden, und seine Schlüssel sind immer Strings oder Symbole. Eine Map startet leer, akzeptiert jeden Wert als Schlüssel und meldet die Anzahl ihrer Einträge über size.
Record ist ein Typ, kein Container. Er beschreibt ein einfaches Objekt, daher gilt alles oben Gesagte über Objekte zur Laufzeit auch für ihn, einschließlich der nativen JSON-Unterstützung, die einer Map fehlt.
Wie Schlüssel abgeglichen werden
Map vergleicht Schlüssel nach der SameValueZero-Regel. Die Zahl 1 und der String "1" sind verschiedene Schlüssel, NaN passt zu NaN, und ein Objekt passt nur zu sich selbst. Ein einfaches Objekt würde 1 in "1" umwandeln und beide zusammenführen.
Das macht eine Map zum natürlichen Cache für Werte, deren Schlüssel ein DOM-Element, eine Funktion oder ein Request-Objekt ist, Fälle, in denen ein Objekt eine erfundene String-id bräuchte.
Maps in Funktionssignaturen typisieren
Akzeptiere in Funktionen, die nur lesen, ReadonlyMap<K, V>. Das entfernt set, delete und clear aus dem Typ, sodass schon die Signatur den Aufrufern sagt, dass ihre Map nicht verändert wird.
Baust du eine Map aus einem Array von Paaren, inferiert TypeScript die Typen aus den Paaren. Gib ein explizites Generic an, wenn das Array leer sein könnte oder die Paare Typen mischen, die du getrennt halten willst.
Rezepte für typisierte Maps
Wörter mit einer Map zählen
Der ??-Fallback behandelt das erste Auftreten eines Worts. Deshalb ist es hilfreich statt lästig, dass get number | undefined zurückgibt.
Eine Map als JSON speichern und wieder laden
Ein Array aus Paaren behält jeden Eintrag und seine Reihenfolge. Object.fromEntries ist die lesbare Wahl, wenn jeder Schlüssel ein String ist.
Eine schreibgeschützte Map akzeptieren
Innerhalb von total wäre ein Aufruf von prices.set ein Compilerfehler, Aufrufer können ihre Map also ohne defensive Kopie übergeben.
Live-Map-Playground
Schlüssel
Wert (Zahl)
Setzen
Löschen
Leeren
Zurücksetzen
Einträge in Iterationsreihenfolge
Die Map ist leer.
size: {n}
"{v}" hinzugefügt.
"{v}" gelöscht.
"{v}" ist nicht in der Map, daher hat delete false zurückgegeben.