Baue einen Record<K, V>-Typ aus deinen eigenen Keys und sieh das Objekt, das er beschreibt. Lerne, wann Record besser ist als eine Index-Signatur oder eine Map, und welche Fehler er abfängt.
Record<K, V> ist ein eingebauter Utility-Typ für ein Objekt, dessen Keys aus K stammen und dessen Werte alle den Typ V haben. Gibst du ihm eine Union aus String-Literalen, besteht der Compiler darauf, dass jeder aufgeführte Key vorhanden ist. Gibst du ihm einfach string, verhält er sich wie ein offenes Dictionary, genau wie eine Index-Signatur.
Record auf einen Blick
Du schreibst
Bedeutet
Fehlender Key erlaubt?
Record<"a" | "b", number>
{ a: number; b: number }
Nein, beide Keys sind Pflicht
Record<string, number>
{ [key: string]: number }
Ja, jeder String-Key
Partial<Record<"a" | "b", number>>
{ a?: number; b?: number }
Ja, jeder Key ist optional
Record<number, string>
{ [key: number]: string }
Ja, jeder Number-Key
Tippe die gewünschten Keys ein und wähle einen Werttyp. Der Builder schreibt den Record-Typ, den einfachen Objekttyp, zu dem er sich auflöst, und ein Startobjekt mit einer Zeile pro Key. So siehst du genau, worauf dich der Compiler festlegt, bevor du es in deinen Code einfügst.
Tippe deine Keys durch Kommas getrennt ein, zum Beispiel home, about, contact.
Wähle den Werttyp, den jeder Key haben soll.
Hake optional an, wenn manche Keys fehlen dürfen. Dann wird der Typ in Partial gehüllt.
Kopiere den Typ und das Startobjekt in deine Datei und trage die Werte ein.
Leite die Key-Union aus Daten ab, die du schon hast: keyof typeof someObject oder (typeof list)[number] nach einem as const-Array hält den Record synchron mit der Quelle.
Greif nur dann zu Record<string, T>, wenn Keys wirklich erst zur Laufzeit ankommen. Eine geschlossene Union fängt Tippfehler in Property-Namen ab, die ein offenes Dictionary durchlässt.
Schalte noUncheckedIndexedAccess ein, wenn du aus Record<string, T> über einen berechneten Key liest. Lesezugriffe liefern dann T | undefined, was dem Verhalten zur Laufzeit entspricht.
Vermeide Record<string, any>. Record<string, unknown> akzeptiert weiterhin jedes Objekt, erzwingt aber eine Prüfung, bevor du einen Wert verwendest.
Häufige Fragen
Ist Record dasselbe wie eine Index-Signatur?
Mit einem breiten Key wie string, ja: Record<string, number> und { [key: string]: number } beschreiben dieselbe Form. Der Unterschied zeigt sich bei Literal-Keys. Eine Index-Signatur kann nicht verlangen, dass bestimmte Namen existieren, während Record<"a" | "b", number> sowohl a als auch b verlangt.
Wann sollte ich eine Map statt eines Records verwenden?
Verwende eine Map, wenn Keys oft hinzugefügt und entfernt werden, wenn Keys keine Strings sind oder wenn du die Anzahl der Einträge über size brauchst. Verwende einen Record für eine feste Menge an Namen, für Konfigurationsobjekte und für alles, was du durch JSON.stringify schickst.
Warum sagt TypeScript, dass in meinem Record eine Property fehlt?
Weil der Key-Typ eine Union ist und das Objekt ein Mitglied auslässt. tsc meldet zum Beispiel Property 'about' is missing in type '{ home: string; }'. Füge den Key hinzu oder hülle den Typ in Partial, wenn das Weglassen zulässig ist.
Existiert Record zur Laufzeit?
Nein. Es ist nur ein Typ und verschwindet beim Kompilieren. Das Objekt, das du erstellst, ist ein ganz normales JavaScript-Objekt, daher kann der Compiler nicht verhindern, dass eine Serverantwort zusätzliche oder fehlende Keys hat.
Mit welcher TypeScript-Version kam Record?
Record kam mit TypeScript 2.1, zusammen mit Partial, Readonly und Pick, daher hat ihn jeder aktuelle Compiler.
Wie Record aufgebaut ist
Record ist ein Mapped Type. Er geht jedes Mitglied des Key-Typs durch und gibt jedem denselben Werttyp. Deshalb löst sich Record<"a" | "b", number> in einen Objekttyp mit zwei verpflichtenden number-Properties auf.
Weil es nur ein Typ ist, kannst du ihm alles geben, was ein Property-Key sein kann: String-Literale, number, string, symbol oder eine Union daraus. Ein Key-Typ, der aus einem anderen Typ gebildet wird, etwa keyof User, aktualisiert sich selbst, wenn sich User ändert.
Geschlossene und offene Keys
Der Nutzen von Record liegt in der geschlossenen Form. Eine Lookup-Tabelle mit Routennamen, Status-Labels oder Theme-Farben, die mit einer Union aus Literalen typisiert ist, lässt den Build an dem Tag scheitern, an dem jemand einen neuen Status ohne Label hinzufügt, und genau dann willst du davon erfahren.
Die offene Form, Record<string, T>, beschreibt ein Dictionary. Sie sagt nichts darüber, welche Keys existieren, daher liefert das Lesen eines Keys T, auch wenn der Key fehlt. Kombiniere sie mit noUncheckedIndexedAccess oder einer Map, wenn diese Lücke wichtig ist.
Record, Index-Signatur oder Map
Nimm Record mit Literal-Keys für feste Lookups, Record<string, T> oder eine Index-Signatur für Dictionaries in JSON-Form und Map für Sammlungen, die sich während der Programmlaufzeit ändern. Die Anleitung zu TypeScript Map auf dieser Website behandelt den letzten Fall ausführlich.
Muster, die du am häufigsten brauchst
Keys aus einem as const-Array
Das Array ist die einzige Quelle der Wahrheit. Fügst du ihm einen Status hinzu, zeigt der Compiler auf die Label-Tabelle, bis du auch das Label ergänzt.
Ein Flag pro Feld eines anderen Typs
keyof macht aus einem bestehenden Typ die Key-Union, daher listet das touched-State-Objekt eines Formulars immer dieselben Felder wie das Formular.
Ein offenes Dictionary, sicher gelesen
Mit aktiviertem noUncheckedIndexedAccess ist ein Lesezugriff über einen berechneten Key number | undefined, daher ist der Fallback Pflicht und wird nicht vergessen.
Record-Typ-Builder
Keys, durch Kommas getrennt
Werttyp
Keys sind optional (in Partial hüllen)
Der Typ
Wozu er sich auflöst
Ein Startobjekt
Tippe mindestens einen Key ein. Keys müssen mit einem Buchstaben, _ oder $ beginnen.