Index Signatures vs. Record: Wann was in TypeScript?
AI generated
type
TypeScript · Typsystem
Index Signatures vs. Record-Typen: Wann was verwenden
Offene Schlüssel oder erzwungene Vollständigkeit: zwei Werkzeuge, zwei völlig unterschiedliche Garantien

Beide Konstrukte typisieren Objekte mit mehreren gleichartigen Werten, doch sie geben dem Compiler grundlegend verschiedene Versprechen. Wer { [key: string]: T } und Record<K, V> für austauschbar hält, produziert entweder zu laxe Typen, die jeden Tippfehler durchwinken, oder zu strenge Typen, die an der Laufzeitrealität scheitern. Dieser Artikel zeigt Syntax, Semantik und die konkrete Entscheidungshilfe für den Alltag.

9 Min. Lesezeit TypeScript Typsystem Mapped Types

1. Zwei Wege, ein Objekt zu typisieren

Sobald ein Objekt mehr als eine Handvoll fest benannter Properties hat oder seine Schlüssel erst zur Laufzeit feststehen, reicht ein klassisches Interface mit einzeln aufgezählten Feldern nicht mehr aus. TypeScript bietet dafür zwei unterschiedliche Werkzeuge: die Index Signature und den Mapped Type Record<K, V>. Beide erlauben es, einen Wertetyp T für eine ganze Klasse von Schlüsseln festzulegen, statt jede Property einzeln zu benennen.

Der entscheidende Unterschied liegt nicht in der Syntax, die sich oberflächlich ähnelt, sondern in der Semantik: Eine Index Signature beschreibt eine offene Menge von Schlüsseln, bei der zusätzliche, dem Compiler unbekannte Keys stillschweigend erlaubt sind. Ein Record mit einem konkreten Union-Typ als Schlüssel beschreibt dagegen eine geschlossene Menge, bei der jeder einzelne Schlüssel vorhanden sein muss und unbekannte Schlüssel abgelehnt werden.

Diese Unterscheidung wirkt zunächst akademisch, hat aber sehr konkrete Konsequenzen für Bugs: Ein falsch gewähltes Konstrukt lässt entweder Tippfehler in Property-Namen unbemerkt durch, oder es zwingt eine Codebasis zu unnötig strikten Vollständigkeitsanforderungen, wo eigentlich Flexibilität gebraucht wird.


// Oberflächlich ähnlich, semantisch grundverschieden
type Dictionary = { [key: string]: string };       // offene Keys
type StatusLabels = Record<"open" | "closed" | "pending", string>; // geschlossene Keys

const d: Dictionary = { hello: "Hallo", anything: "ok" }; // beliebige Keys erlaubt
const s: StatusLabels = { open: "Offen", closed: "Geschlossen", pending: "Ausstehend" };
// s ohne "pending" wäre ein Compile-Fehler, d.irgendeinKey ist immer erlaubt

2. Index Signatures: Syntax und Semantik

Die klassische Index Signature { [key: string]: T } teilt dem Compiler mit, dass ein Objekt beliebig viele Properties besitzen kann, deren Namen vom Typ string sind und deren Werte alle dem Typ T entsprechen. Analog dazu erlaubt [index: number]: T numerische Indizes, wie sie etwa Array-artige Strukturen brauchen. Wichtig dabei: In JavaScript werden Objektschlüssel intern ohnehin als Strings behandelt, eine numerische Index Signature ist also eine zusätzliche, striktere Sicht auf denselben Mechanismus, keine grundsätzlich andere Speicherform.

Seit TypeScript 4.4 sind Index Signatures nicht mehr auf string und number beschränkt. Erlaubt sind jetzt auch Template-Literal-Typen sowie Unions aus solchen Typen, und zusätzlich symbol als Schlüsseltyp. Damit lassen sich etwa alle Schlüssel, die mit einem bestimmten Präfix beginnen, gezielt typisieren, während andere Schlüsselformen weiterhin abgelehnt werden.

Eine Besonderheit: Innerhalb eines Objekttyps dürfen mehrere Index Signatures koexistieren, solange ihre Wertetypen miteinander kompatibel sind, etwa eine string-Signature und eine number-Signature nebeneinander. Zusätzlich definierte, konkret benannte Properties müssen dem Wertetyp der passenden Index Signature entsprechen, sonst meldet der Compiler einen Fehler.


// string-Index-Signature
interface Translations {
  [key: string]: string;
}

// number-Index-Signature (typischerweise für Array-ähnliche Strukturen)
interface SparseArray {
  [index: number]: string;
}

// Seit TS 4.4: Template-Literal- und Union-Index-Signatures
type CssVariables = {
  [key: `--${string}`]: string;   // nur Custom-Properties wie "--primary-color"
};

type EventKeys = {
  [key: `on${string}`]: (event: Event) => void; // nur "onClick", "onSubmit" usw.
};

// symbol als Schlüsseltyp
type SymbolIndexed = {
  [key: symbol]: unknown;
};

const vars: CssVariables = { "--primary-color": "#0ea5e9" }; // ok
// const bad: CssVariables = { color: "red" };                // Fehler: "color" passt nicht zum Pattern

3. Record: Der Mapped Type aus lib.es5.d.ts

Record<K, V> ist kein eingebautes Sprachfeature im engeren Sinn, sondern ein in lib.es5.d.ts mitgelieferter Mapped Type. Seine Definition ist denkbar kompakt: type Record<K extends keyof any, T> = { [P in K]: T }. Der Compiler iteriert dabei über jeden Schlüssel der Union K und erzeugt für jeden davon eine eigene, benannte Property vom Typ T.

Der Constraint K extends keyof any bedeutet, dass K ein string, number oder symbol sein muss, da genau diese drei Typen als Objektschlüssel in JavaScript zulässig sind. In der Praxis wird K fast immer ein Literal-Union-Typ wie "open" | "closed" | "pending" oder ein via keyof abgeleiteter Typ sein.

Weil Record intern über [P in K] iteriert, entstehen für jeden Wert der Union K jeweils konkrete, benannte Properties, keine generische Index Signature. Das ist der technische Grund dafür, dass sich Record und Index Signature bei der Vollständigkeitsprüfung und bei noUncheckedIndexedAccess unterschiedlich verhalten, wie die folgenden Abschnitte zeigen.


// Vereinfachte Definition aus lib.es5.d.ts
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

// Typische Anwendung: bekannte, endliche Schlüsselmenge
type FeatureFlags = Record<"darkMode" | "betaCheckout" | "newSearch", boolean>;

const flags: FeatureFlags = {
  darkMode: true,
  betaCheckout: false,
  newSearch: true,
  // jeder Schlüssel der Union MUSS vorhanden sein
};

// Record<string, T> ist ebenfalls gültig, verhält sich dann aber
// im Kern wie eine Index Signature (siehe nächster Abschnitt)
type LooseDictionary = Record<string, number>;

4. Der Kernunterschied: offene Keys vs. erzwungene Vollständigkeit

Der praktisch wichtigste Unterschied zeigt sich, sobald K in Record<K, V> ein konkreter Literal-Union-Typ ist, etwa "a" | "b" | "c": Der Compiler erzwingt dann Vollständigkeit. Jeder einzelne Schlüssel der Union muss im Objektliteral vorkommen, sonst schlägt die Zuweisung fehl. Gleichzeitig lehnt TypeScript jeden Schlüssel ab, der nicht Teil der Union ist, ein klassischer Fall von Excess Property Check in Kombination mit der Struktur des Mapped Types.

Eine Index Signature verhält sich diametral entgegengesetzt: Sie beschreibt lediglich eine obere Grenze für erlaubte Schlüssel und deren Werttyp, erzwingt aber nie, dass ein bestimmter Schlüssel tatsächlich vorhanden ist. Ein leeres Objekt {} ist beispielsweise gültig für { [key: string]: string }, während dasselbe leere Objekt für Record<"a" | "b", string> abgelehnt wird, weil weder a noch b vorhanden sind.

Wird Record<string, T> statt eines konkreten Unions verwendet, verschwindet dieser Unterschied fast vollständig: string als Schlüsseltyp ist selbst keine endliche, aufzählbare Menge, weshalb TypeScript hier keine Vollständigkeit erzwingen kann. Record<string, T> verhält sich strukturell praktisch identisch zu { [key: string]: T }, das ist auch der Grund, warum viele Codebasen Record<string, T> als lesbarere Alternative zur klassischen Index-Signature-Syntax bevorzugen.


type OpenDict = { [key: string]: number };
type ClosedRecord = Record<"a" | "b", number>;

const open1: OpenDict = {};                 // ok, keine Schlüssel erforderlich
const open2: OpenDict = { anything: 1 };    // ok, unbekannter Key erlaubt

// const closed1: ClosedRecord = {};        // Fehler: "a" und "b" fehlen
const closed2: ClosedRecord = { a: 1, b: 2 };            // ok, vollständig
// const closed3: ClosedRecord = { a: 1, b: 2, c: 3 };   // Fehler: "c" ist unbekannt

// Record<string, T> nähert sich wieder der Index Signature an
type NearlyOpen = Record<string, number>;
const near: NearlyOpen = {};                // ok, string ist keine endliche Menge

5. Das keyof typeof Pattern

In der Praxis existiert eine Schlüsselmenge häufig schon als konkretes Objektliteral, etwa eine Konfiguration oder ein Enum-artiges Objekt, bevor überhaupt ein passender Typ dafür geschrieben wird. Statt die Union der Schlüssel manuell und redundant zu pflegen, lässt sie sich mit dem keyof typeof Pattern direkt aus dem Objekt ableiten: typeof obj liefert den konkreten Objekttyp mit allen literalen Property-Namen, keyof extrahiert daraus die Union dieser Namen.

Dieses Pattern ist besonders wertvoll, um einen Record-Key-Typ synchron zu einer bereits existierenden Datenquelle zu halten. Ändert sich das Ausgangsobjekt, etwa weil ein neuer Status hinzukommt, aktualisiert sich der abgeleitete Union-Typ automatisch mit, ohne dass an zwei Stellen im Code manuell nachgezogen werden muss.

Wichtig ist dabei as const auf dem Ausgangsobjekt, sofern die Werte selbst als Literal-Typen erhalten bleiben sollen, für die reine Schlüssel-Ableitung über keyof typeof ist as const jedoch nicht zwingend erforderlich, da keyof ohnehin nur die Property-Namen betrachtet, nicht deren Werttypen.


// Ausgangspunkt: ein Objektliteral definiert die Quelle der Wahrheit
const orderStatus = {
  pending: "Ausstehend",
  shipped: "Versendet",
  delivered: "Zugestellt",
  cancelled: "Storniert",
} as const;

// keyof typeof leitet die Union der Schlüssel automatisch ab
type OrderStatusKey = keyof typeof orderStatus;
// entspricht: "pending" | "shipped" | "delivered" | "cancelled"

// Damit lässt sich ein passender Record bauen, der zwangsläufig
// synchron zum Ausgangsobjekt bleibt
type StatusColor = Record<OrderStatusKey, string>;

const statusColors: StatusColor = {
  pending: "amber",
  shipped: "blue",
  delivered: "green",
  cancelled: "red",
};

function label(key: OrderStatusKey): string {
  return orderStatus[key];
}

6. noUncheckedIndexedAccess: Sicherheit bei Indexzugriffen

Standardmäßig behandelt TypeScript den Zugriff über eine Index Signature so, als sei der Schlüssel garantiert vorhanden: dict[someKey] liefert den Typ T, obwohl zur Laufzeit durchaus undefined herauskommen kann, wenn der Schlüssel tatsächlich fehlt. Das Compiler-Flag noUncheckedIndexedAccess schließt genau diese Lücke: Ist es aktiv, liefert jeder Zugriff über eine Index Signature den Typ T | undefined statt nur T, und zwingt den Code dazu, den undefined-Fall explizit zu behandeln.

Entscheidend für das Verständnis: noUncheckedIndexedAccess betrifft grundsätzlich jeden Zugriff über eine Index Signature, unabhängig davon, ob diese über die klassische { [key: string]: T } Syntax oder über Record<string, T> geschrieben wurde, denn beide erzeugen strukturell dieselbe Index Signature. Der Zugriff Record Instanz[someString] wird also ebenso zu T | undefined wie der Zugriff auf ein Objekt mit klassischer Index Signature.

Anders verhält es sich bei Record mit einem konkreten Literal-Union als Schlüssel: Der Zugriff auf einen bekannten, literalen Schlüssel wie statusColors.pending oder statusColors["pending"] wird von TypeScript wie der Zugriff auf eine benannte, garantiert vorhandene Property behandelt, nicht wie ein Index-Signature-Zugriff. Deshalb bleibt der Typ hier T, auch bei aktivem noUncheckedIndexedAccess, denn strukturell entsteht durch den Mapped Type ja eine konkrete Property und keine generische Index Signature. Wird jedoch mit einer nicht literalen Variable vom Typ OrderStatusKey indexiert, etwa statusColors[variableKey], greift dieselbe Absicherung wieder, da TypeScript hier konservativ mit einem indizierten Zugriff auf einen Union-Typ umgeht.


// tsconfig.json: { "compilerOptions": { "noUncheckedIndexedAccess": true } }

const dict: { [key: string]: number } = { a: 1, b: 2 };
const v1 = dict["a"];          // Typ: number | undefined

const recordLoose: Record<string, number> = { a: 1, b: 2 };
const v2 = recordLoose["a"];   // Typ: number | undefined, gleiches Verhalten wie oben

const recordStrict: Record<"a" | "b", number> = { a: 1, b: 2 };
const v3 = recordStrict.a;         // Typ: number, literaler Key = benannte Property
const v4 = recordStrict["a"];      // Typ: number, gleiches Verhalten

function readDynamic(key: "a" | "b") {
  return recordStrict[key];        // Typ: number, key ist auf die Union eingeschränkt
}

7. Mapped Types: Record ist nur ein Spezialfall

Record ist letztlich nur die einfachste, vorgefertigte Ausprägung eines allgemeineren Konzepts: dem Mapped Type. Die Syntax { [P in K]: T } lässt sich beliebig erweitern, etwa um readonly und optionale Modifier, um Werttypen, die selbst vom jeweiligen Schlüssel P abhängen, oder um Key-Remapping über die as-Klausel, eingeführt in TypeScript 4.1.

Während Record<K, T> jedem Schlüssel denselben festen Typ T zuweist, kann ein selbst geschriebener Mapped Type den Werttyp pro Schlüssel individuell berechnen, etwa mit { [P in K]: SomeType[P] } zur Ableitung aus einem bestehenden Typ, wie es Utility-Types wie Partial, Readonly oder Pick intern tun. Diese Utility-Types sind selbst nichts anderes als spezialisierte Mapped Types, die dieselbe Grundmechanik wie Record nutzen, nur mit anderen Modifiern und Quellausdrücken.

Für die Praxis bedeutet das: Sobald ein einfacher Record<K, T> nicht mehr ausreicht, weil unterschiedliche Schlüssel unterschiedliche Werttypen brauchen oder weil Schlüssel umbenannt werden müssen, ist der nächste Schritt kein Bruch mit dem bisherigen Modell, sondern lediglich das Ausschreiben derselben Mapped-Type-Syntax mit zusätzlichen Fähigkeiten.


// Record ist die einfachste Form eines Mapped Type
type Simple = Record<"a" | "b", number>;
// entspricht exakt:
type SimpleExplicit = { [P in "a" | "b"]: number };

// Mapped Types können mehr: readonly, optional, Key-Remapping (TS 4.1+)
type ReadonlyPartial<K extends string, T> = {
  readonly [P in K]?: T;
};

// Werttyp pro Schlüssel individuell ableiten (wie Utility-Types es intern tun)
type FieldValidators<T> = {
  [P in keyof T]: (value: T[P]) => boolean;
};

interface Product {
  name: string;
  price: number;
}

const validators: FieldValidators<Product> = {
  name: (value) => value.length > 0,
  price: (value) => value > 0,
};

// Key-Remapping mit der "as"-Klausel
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
// ergibt für Product: { getName: () => string; getPrice: () => number }

8. Faustregel: Wann welches Werkzeug?

Die praktische Entscheidung lässt sich auf eine einzige Frage reduzieren: Ist die Menge der Schlüssel zur Compile-Zeit bekannt und endlich, oder erst zur Laufzeit? Bei einer dynamischen, zur Compile-Zeit unbekannten Schlüsselmenge, etwa einem Übersetzungs-Dictionary, das aus einer JSON-Datei geladen wird, einem Cache mit beliebigen Cache-Keys, oder Query-Parametern einer URL, ist eine Index Signature oder Record<string, T> das passende Werkzeug. Der Compiler kann hier ohnehin keine sinnvolle Vollständigkeit prüfen, weil die Schlüssel schlicht nicht im Typsystem existieren.

Bei einer bekannten, endlichen Schlüsselmenge, etwa einem Status-Enum, den Werten eines Feature-Flag-Systems, den erlaubten HTTP-Methoden oder den Konfigurationsoptionen pro Feature, ist Record<K, V> mit einem konkreten Literal-Union die bessere Wahl. Der entscheidende Gewinn ist die Vollständigkeitsprüfung: Vergisst ein Entwickler einen Status beim Anlegen eines neuen Konfigurationsobjekts, meldet der Compiler den Fehler sofort, statt dass er erst zur Laufzeit als fehlender Eintrag auffällt.

Eine nützliche Kontrollfrage in Grenzfällen: Würde ein neuer, unbekannter Schlüssel im Code eher als Feature (mehr Übersetzungen, mehr Cache-Einträge) oder als Bug (ein Status, der im Enum fehlt) gewertet? Im ersten Fall passt die offene Index Signature, im zweiten Fall der geschlossene Record.


// Dynamische, unbekannte Keys: Uebersetzungen aus einer JSON-Datei
type Translations = { [key: string]: string };
// oder äquivalent: Record<string, string>

async function loadTranslations(locale: string): Promise<Translations> {
  const res = await fetch(`/i18n/${locale}.json`);
  return res.json();
}

// Bekannte, endliche Keys: Feature-Flags pro Feature
type FeatureFlags = Record<"darkMode" | "newCheckout" | "betaSearch", boolean>;

const flags: FeatureFlags = {
  darkMode: true,
  newCheckout: false,
  betaSearch: true,
  // ein vergessener Flag wäre sofort ein Compile-Fehler
};

9. Best Practices und Fazit

Ein häufiger Kompromiss in gewachsenen Codebasen ist Record<string, T> als lesbarere, konsistentere Alternative zur klassischen Index-Signature-Syntax, ohne dass dabei semantisch etwas verloren geht, da beide Formen strukturell äquivalent sind. Für neue Konfigurationsobjekte, Status-Mappings und Ähnliches lohnt sich fast immer der Griff zum konkreten Literal-Union über Record, die Vollständigkeitsprüfung fängt eine ganze Klasse von Bugs bereits beim Kompilieren ab.

noUncheckedIndexedAccess sollte in jedem neuen TypeScript-Projekt aktiviert werden, sobald irgendwo Index Signatures oder Record<string, T> im Spiel sind, denn ohne das Flag verstecken sich potenzielle undefined-Zugriffe hinter einem vermeintlich sicheren Typ T. Die dadurch erzwungenen Prüfungen, etwa via optional Chaining oder expliziten Existenz-Checks, sind kein Overhead, sondern machen implizite Annahmen über Datenvollständigkeit im Code sichtbar.

Zusammengefasst: Index Signatures modellieren Unsicherheit über die Schlüsselmenge ehrlich, Record mit konkretem Union-Typ modelliert Gewissheit und erzwingt sie auch. Wer beide Werkzeuge nach dieser Leitlinie einsetzt, statt sie austauschbar zu behandeln, gewinnt an Stellen, an denen es zählt, nämlich beim Kompilieren, echte Fehlerprävention und verliert an Stellen, an denen echte Flexibilität nötig ist, keine Ausdrucksstärke.


// Faustregel als Code-Kommentar zusammengefasst
// 1. Schlüssel dynamisch/unbekannt zur Compile-Zeit -> Index Signature / Record<string, T>
// 2. Schlüssel bekannt/endlich -> Record<"a" | "b" | "c", T> (erzwingt Vollständigkeit)
// 3. noUncheckedIndexedAccess IMMER aktivieren, sobald Index Signatures im Spiel sind
// 4. Braucht es pro Schlüssel einen unterschiedlichen Werttyp -> eigener Mapped Type

interface TsConfigExcerpt {
  compilerOptions: {
    strict: true;
    noUncheckedIndexedAccess: true;
  };
}
Merkmal Index Signature Record (Union-K) Typischer Einsatz
Unbekannte Zusatz-Keys Erlaubt, stillschweigend akzeptiert Abgelehnt, Compile-Fehler Dictionary vs. Enum-Mapping
Vollständigkeit erzwungen Nein, leeres Objekt ist gültig Ja, jeder Union-Key ist Pflicht Feature-Flags pro Feature
Zugriff mit noUncheckedIndexedAccess Immer T | undefined T bei literalem Key, T | undefined bei Union-Variable Sichere Zugriffe auf Caches
Herkunft im Typsystem Eingebautes Sprachfeature Mapped Type aus lib.es5.d.ts Beide seit frühen TS-Versionen
Verallgemeinerung Keine, feste Grundform Spezialfall von { [P in K]: T } Custom Mapped Types mit as-Klausel
Passt zu keyof typeof Selten sinnvoll Ideal, Union direkt aus Objekt ableitbar Status-Mappings aus Konfiguration

Mironsoft

TypeScript-Migration, Typsicherheit und Team-Onboarding

JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?

Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.

Migrations-Fahrplan

Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.

Strict-Mode-Einführung

tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.

Team-Onboarding

Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.

10. Zusammenfassung

Index Signatures vs. Record

Offene Keys

Index Signature akzeptiert beliebige zusätzliche String-Keys stillschweigend

Erzwungene Vollständigkeit

Record mit Union-K verlangt jeden Schlüssel und lehnt unbekannte ab

noUncheckedIndexedAccess

Macht jeden Index-Signature-Zugriff zu T | undefined, literale Record-Keys ausgenommen

Faustregel

Bekannte, endliche Keys: Record. Dynamische, unbekannte Keys: Index Signature

11. FAQ: Index Signatures vs. Record

1Ist Record dasselbe wie eine Index Signature?
Strukturell praktisch ja. Sobald der Schlüsseltyp von Record ein einfacher string, number oder symbol ist statt eines konkreten Literal-Unions, verhält sich der resultierende Typ wie eine klassische Index Signature: beliebige zusätzliche Schlüssel sind erlaubt, Vollständigkeit wird nicht geprüft, und noUncheckedIndexedAccess wirkt genauso darauf.
2Warum akzeptiert TypeScript kein leeres Objekt für Record mit Union-Key?
Weil Record intern über jeden einzelnen Wert der Union K iteriert und für jeden davon eine eigene, benannte Property erzeugt. Ein leeres Objekt erfüllt diese erzeugten Properties nicht, deshalb meldet der Compiler einen Fehler, genau wie bei einem Interface mit mehreren Pflichtfeldern.
3Kann ich eine Index Signature und benannte Properties im selben Typ mischen?
Ja. TypeScript erlaubt zusätzlich zur Index Signature konkret benannte Properties, verlangt aber, dass deren Werttyp mit dem Werttyp der Index Signature kompatibel ist. Eine Property mit einem inkompatiblen Typ führt zu einem Compile-Fehler.
4Was macht noUncheckedIndexedAccess konkret mit Record?
Jeder Zugriff über einen dynamischen oder auch literalen String-Schlüssel liefert dann T | undefined statt nur T, weil Record strukturell eine Index Signature erzeugt. Das gilt unabhängig davon, ob mit eckigen Klammern oder Punktnotation zugegriffen wird.
5Warum liefert der Zugriff auf einen literalen Key bei Record<'a'|'b', T> kein undefined?
Weil der Mapped Type für jeden Wert der Union eine konkrete, benannte Property erzeugt, keine generische Index Signature. TypeScript behandelt den Zugriff auf einen bekannten Literal-Key deshalb wie den Zugriff auf eine garantiert vorhandene Property und nicht wie einen unsicheren Indexzugriff.
6Was ist das keyof typeof Pattern und wofür wird es genutzt?
Es leitet einen Union-Typ aus den Schlüsseln eines existierenden Objektliterals ab: typeof liefert den konkreten Objekttyp, keyof extrahiert daraus die Schlüssel-Union. Damit lässt sich ein Record-Key-Typ synchron zu einer bestehenden Datenquelle halten, ohne die Schlüssel doppelt zu pflegen.
7Brauche ich as const für keyof typeof?
Für die reine Schlüssel-Ableitung nicht zwingend, da keyof nur die Property-Namen betrachtet. as const wird relevant, wenn zusätzlich auch die Werte des Objekts als Literal-Typen erhalten bleiben sollen, statt zu generischen Typen wie string oder number verbreitert zu werden.
8Ist Record ein eingebautes TypeScript-Sprachfeature?
Nein. Record ist ein in lib.es5.d.ts mitgelieferter, vordefinierter Mapped Type, definiert als type Record = { [P in K]: T }. Er nutzt dieselbe Mapped-Type-Syntax, die auch für eigene Utility-Types wie Partial oder Readonly verwendet wird.
9Wann lohnt sich ein eigener Mapped Type statt Record?
Sobald unterschiedliche Schlüssel unterschiedliche Werttypen brauchen, sobald Modifier wie readonly oder optional pro Schlüssel gesteuert werden sollen, oder sobald Schlüssel über eine as-Klausel umbenannt werden müssen. Record deckt nur den Fall ab, dass alle Schlüssel denselben festen Werttyp T bekommen.
10Welches Werkzeug passt zu einem Uebersetzungs-Dictionary aus einer JSON-Datei?
Eine Index Signature oder gleichwertig Record, da die konkreten Schlüssel erst zur Laufzeit beim Laden der JSON-Datei bekannt sind und der Compiler sie nicht im Voraus prüfen kann. Ein Versuch, hier Vollständigkeit über einen Literal-Union zu erzwingen, würde ständig unpassende Compile-Fehler erzeugen.