Zum Inhalt springen
aviral gupta

// B5.2 · ca. 30 Min. · Einstieg

Index-Signaturen

Nach dieser Lektion typisieren Sie Objekte mit vorab unbekannten Schlüsseln, kombinieren sie mit benannten Eigenschaften und behandeln fehlende Schlüssel.

Lektion 2 von 5 in B5 Objekttypen

Danach können Sie

  • Objekte mit unbekannten Schlüsseln über string- und number-Index-Signaturen oder Record<string, T> typisieren
  • Benannte Eigenschaften an eine Index-Signatur anpassen, error TS2411 mit einer Union beheben und die Signatur readonly machen
  • Zur Laufzeit fehlende Schlüssel behandeln und erklären, was noUncheckedIndexedAccess ändert
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen mit JavaScript: ein Objekt als Wörterbuch. Was gibt das aus?

    const stock = {};
    stock["apples"] = 3;
    stock.pears = 2;
    console.log(stock.apples + stock["pears"], stock.plums);
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: item ist als string annotiert. Welche Zeile lehnt tsc ab?

    const prices = { apple: 0.5, pear: 0.75 };
    const item: string = "apple";
    console.log(prices[item]);
  3. Üben · Aufgabe 3 von 7

    Füllen Sie den Schlüsseltyp ein, sodass jeder Name als Schlüssel dienen kann und das Programm 254 ausgibt.

    interface Scores {
      [name: ____]: number;
    }
    
    const scores: Scores = { Ada: 91, Grace: 78 };
    scores.Linus = 85;
    console.log(scores.Ada + scores.Grace + scores.Linus);
    [name: ]: number;
  4. Üben · Aufgabe 4 von 7

    Mit welcher Index-Signatur anstelle von ____ akzeptiert tsc das Interface?

    interface NumberOrStringDictionary {
      ____
      length: number;
      name: string;
    }
  5. Üben · Aufgabe 5 von 7

    Die Index-Signatur ist readonly. Welche Zeile lehnt tsc ab?

    interface ReadonlyStringArray {
      readonly [index: number]: string;
    }
    
    const myArray: ReadonlyStringArray = ["Alice", "Bob"];
    console.log(myArray[0]);
    myArray[2] = "Mallory";
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Für Zoe hat niemand Punkte eingetragen. Was passiert, wenn Sie npx tsc und dann node main.ts ausführen?

    interface Scores {
      [name: string]: number;
    }
    
    const scores: Scores = { Ada: 91, Grace: 78 };
    const missing = scores["Zoe"];
    console.log(missing);
    console.log(missing.toFixed(1));
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Schreiben Sie ein Interface CountryNames mit einer readonly-String-Index-Signatur, deren Werte Ländernamen sind, und ein Objekt countries mit zwei oder drei Einträgen wie DE: "Germany". Schreiben Sie countryName(code), das mit in prüft, ob der Code existiert, und unknown (XX) zurückgibt, wenn nicht. Rufen Sie es mit einem bekannten und einem unbekannten Code auf. Versuchen Sie dann countries.IT = "Italy" und lesen Sie den Fehler. Prüfen Sie es im Editor oder mit npx tsc und node main.ts.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Ein Notenbuch

Scores ordnet jedem Schülernamen eine Zahl zu. ClassInfo hat neben seiner Index-Signatur zwei benannte Eigenschaften, also ist der Index-Typ eine Union, zu der beide passen. GradeLimits ist readonly. Object.entries geht die Schlüssel durch, und scoreOf prüft mit in, bevor es einen möglicherweise fehlenden Schlüssel liest. Führen Sie npx tsc und dann node main.ts aus. Fügen Sie danach limits.D = 50; oder eine benannte Eigenschaft active: boolean in ClassInfo hinzu und führen Sie tsc erneut aus.

main.ts

// Scores per student: the names are not known when the type is written.
interface Scores {
  [student: string]: number;
}

// A named property must fit the index type, so the index type is a union.
interface ClassInfo {
  [key: string]: string | number;
  name: string;
  size: number;
}

// readonly: the grade limits can be read, not changed.
interface GradeLimits {
  readonly [grade: string]: number;
}

const scores: Scores = { Ada: 91, Grace: 78 };
scores["Linus"] = 64;

const info: ClassInfo = { name: "7b", size: 3, room: "B12" };
const limits: GradeLimits = { A: 90, B: 75, C: 60 };

function gradeOf(score: number): string {
  for (const [grade, min] of Object.entries(limits)) {
    if (score >= min) return grade;
  }
  return "F";
}

for (const [student, score] of Object.entries(scores)) {
  console.log(student + ": " + score + " (" + gradeOf(score) + ")");
}

// A key nobody set is undefined at run time, whatever the type says: check first.
function scoreOf(student: string): string {
  if (!(student in scores)) return student + ": no score";
  return student + ": " + scores[student];
}
console.log(scoreOf("Zoe"));
console.log("class " + info.name + " in room " + info.room);

Ausführen mit

npx tsc
node main.ts

Ausgabe

Ada: 91 (A)
Grace: 78 (B)
Linus: 64 (C)
Zoe: no score
class 7b in room B12
  • scores["Linus"] = 64 fügt zur Laufzeit einen Schlüssel hinzu; die Index-Signatur erlaubt das schon.
  • info.room ist keine benannte Eigenschaft, also kommt sein Typ von der Index-Signatur: string | number.
  • limits.D = 50 wäre error TS2542: Index signature in type 'GradeLimits' only permits reading.
  • Ohne die Prüfung mit in wäre scores[student] für Zoe undefined, obwohl der Typ number ist.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Stimmen zählen

countVotes zählt, wie oft jede Option gewählt wurde, aber counts = {} hat keine Schlüssel, also meldet tsc error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. Exportieren Sie ein Interface VoteCounts mit einer String-Index-Signatur, deren Werte Zahlen sind. Annotieren Sie counts damit, und geben Sie countVotes den Rückgabetyp VoteCounts. Führen Sie die Tests aus: Der erste ist die Typprüfung.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Eine Index-Signatur steht im Interface: [option: string]: number;

  2. Hinweis 2

    Schreiben Sie export interface VoteCounts { … } über die Funktion, dann const counts: VoteCounts = {};

  3. Hinweis 3

    Der Rückgabetyp steht hinter der Parameterliste: countVotes(votes: string[]): VoteCounts {

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export interface VoteCounts {
  [option: string]: number;
}

export function countVotes(votes: string[]): VoteCounts {
  const counts: VoteCounts = {};
  for (const vote of votes) {
    counts[vote] = (counts[vote] ?? 0) + 1;
  }
  return counts;
}
Auf dem eigenen Computer ausführen

Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.ts

export function countVotes(votes: string[]) {
  const counts = {};
  for (const vote of votes) {
    counts[vote] = (counts[vote] ?? 0) + 1;
  }
  return counts;
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {countVotes} from './main.ts';

test('jede Option wird gezählt', () => {
  const got = countVotes(['tea', 'coffee', 'tea']);
  assert.deepEqual(got, {tea: 2, coffee: 1}, `countVotes ergab ${JSON.stringify(got)}`);
});

test('keine Stimmen ergeben ein leeres Objekt', () => {
  const got = countVotes([]);
  assert.deepEqual(got, {}, `countVotes([]) ergab ${JSON.stringify(got)}`);
});

test('eine Option ohne Stimme fehlt', () => {
  const got = countVotes(['tea']);
  assert.equal(got['water'], undefined, `water sollte fehlen, ist aber ${got['water']}`);
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.

Programm ausführen:

npx tsc
node main.ts

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npx tsc
node --test
learnrun.js herunterladen

Übung 2 von 2

Schreibgeschützte Texte mit Version

Messages enthält Texte nach Schlüssel und dazu eine Versionsnummer. tsc meldet error TS2411: Property 'version' of type 'number' is not assignable to 'string' index type 'string'., und das // @ts-expect-error in noEdits ist unbenutzt, weil sich messages noch ändern lässt. Machen Sie den Index-Typ zu einer Union, zu der version passt, und machen Sie die Index-Signatur readonly. Korrigieren Sie dann translate: Es gibt den Text in Großbuchstaben zurück oder den Schlüssel in eckigen Klammern, etwa [missing], wenn es zu dem Schlüssel keinen Text gibt, auch für version.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Der Index-Typ muss Texte und die Version erlauben: readonly [key: string]: string | number;

  2. Hinweis 2

    messages[key] ist dann string | number und kann zur Laufzeit undefined sein. typeof text !== "string" fängt eine Zahl und undefined auf einmal ab.

  3. Hinweis 3

    const text = messages[key]; if (typeof text !== "string") return "[" + key + "]"; return text.toUpperCase();

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export interface Messages {
  readonly [key: string]: string | number;
  version: number;
}

export const en: Messages = { version: 2, greeting: "Hello", farewell: "Goodbye" };

export function translate(messages: Messages, key: string): string {
  const text = messages[key];
  if (typeof text !== "string") return "[" + key + "]";
  return text.toUpperCase();
}

function noEdits(messages: Messages) {
  // @ts-expect-error: messages are read-only
  messages["greeting"] = "Hi";
}
Auf dem eigenen Computer ausführen

Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.ts

export interface Messages {
  [key: string]: string;
  version: number;
}

export const en: Messages = { version: 2, greeting: "Hello", farewell: "Goodbye" };

export function translate(messages: Messages, key: string): string {
  return messages[key].toUpperCase();
}

function noEdits(messages: Messages) {
  // @ts-expect-error: messages are read-only
  messages["greeting"] = "Hi";
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {en, translate} from './main.ts';

test('ein bekannter Schlüssel ergibt den Text in Großbuchstaben', () => {
  const got = translate(en, 'greeting');
  assert.equal(got, 'HELLO', `translate(en, 'greeting') ergab ${JSON.stringify(got)}`);
});

test('ein fehlender Schlüssel ergibt den Schlüssel in Klammern', () => {
  const got = translate(en, 'missing');
  assert.equal(got, '[missing]', `translate(en, 'missing') ergab ${JSON.stringify(got)}`);
});

test('version ist kein Text', () => {
  const got = translate(en, 'version');
  assert.equal(got, '[version]', `translate(en, 'version') ergab ${JSON.stringify(got)}`);
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.

Programm ausführen:

npx tsc
node main.ts

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npx tsc
node --test
learnrun.js herunterladen

Häufige Fehler

Eine benannte Eigenschaft, die nicht zum Index-Typ passt

interface Stock {
  [product: string]: number;
  updated: string;
}

Was tsc oder Node.js ausgibt

main.ts(3,3): error TS2411: Property 'updated' of type 'string' is not assignable to 'string' index type 'number'.

Warum, und die Lösung

Die Index-Signatur sagt, jeder Schlüssel liefert eine Zahl, und updated ist auch ein Schlüssel: stock["updated"] muss eine Zahl sein. Erweitern Sie entweder den Index-Typ, [product: string]: number | string, dann liest sich jedes Produkt als number | string, oder legen Sie die Mengen in eine eigene Eigenschaft: { updated: string; counts: { [product: string]: number } }.

Mit einem beliebigen String auf ein festes Objekt zugreifen

const prices = { apple: 0.5, pear: 0.75 };
const item: string = "apple";
console.log(prices[item]);

Was tsc oder Node.js ausgibt

main.ts(3,13): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ apple: number; pear: number; }'.

Warum, und die Lösung

prices hat zwei bekannte Schlüssel, und item kann ein beliebiger String sein, also kann tsc nicht sagen, was prices[item] ist. Sind die Schlüssel offen, sagen Sie es: const prices: { [item: string]: number } = { … }, und prüfen Sie auf fehlende Schlüssel. Ist item immer apple oder pear, geben Sie ihm diesen Typ: const item: "apple" | "pear" = "apple".

Über eine readonly-Index-Signatur schreiben

interface Limits {
  readonly [grade: string]: number;
}

const limits: Limits = { A: 90, B: 75 };
console.log(limits.A);
limits.C = 60;

Was tsc oder Node.js ausgibt

main.ts(7,1): error TS2542: Index signature in type 'Limits' only permits reading.

Warum, und die Lösung

readonly gilt für jeden Schlüssel, den die Signatur abdeckt, auch für noch nicht vorhandene: limits.C = 60 fügt einen hinzu, und das ist ein Schreibzugriff. Geben Sie C seinen Wert beim Erzeugen des Objekts, { A: 90, B: 75, C: 60 }, oder erzeugen Sie ein neues Objekt: const more: Limits = { ...limits, C: 60 }.

TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Eine Index-Signatur beschreibt Schlüssel, die Sie noch nicht kennen

Manchmal kennen Sie die Form der Werte, aber nicht die Namen der Schlüssel: Punkte pro Schüler, Stimmen pro Option. interface Scores { [name: string]: number } sagt, dass jeder String-Schlüssel eine Zahl liefert. scores.Linus = 85 und scores["Ada"] sind beide in Ordnung, und ein Wert vom falschen Typ ist error TS2322. Ohne Index-Signatur ist prices[item] mit einer String-Variablen error TS7053: Element implicitly has an 'any' type …. Eine number-Index-Signatur, [index: number]: string, beschreibt Array-ähnliche Objekte. Schlüssel dürfen string, number, symbol, Template-String-Muster oder Unions daraus sein; [key: boolean] ist error TS1268. Record<string, number>, das bei den Utility Types wiederkommt, bedeutet dasselbe wie { [key: string]: number }.

Benannte Eigenschaften müssen zum Index-Typ passen

Ein String-Index deckt jeden Schlüssel ab, auch die benannten, also muss jede benannte Eigenschaft den Index-Typ haben: { [index: string]: number; length: number; name: string } ist error TS2411: Property 'name' of type 'string' is not assignable to 'string' index type 'number'. Eine optionale Eigenschaft zählt als number | undefined, also scheitert bonus?: number auch. Die Lösung ist eine Union: [index: string]: number | string. Dann passen length und name, aber jeder andere Schlüssel liest sich ebenfalls als number | string. Mit einem number- und einem string-Index muss der number-Index zum string-Index passen (TS2413): 100 und "100" sind derselbe Schlüssel. readonly [index: number]: string verbietet Schreiben: error TS2542: Index signature … only permits reading.

Ein fehlender Schlüssel ist undefined, egal was der Typ sagt

Der Typ sagt, jeder Schlüssel liefert eine Zahl, aber zur Laufzeit liefert ein nie gesetzter Schlüssel undefined. const missing = scores["Zoe"] hat den Typ number, tsc schweigt, und missing.toFixed(1) bricht mit einem TypeError ab. Eine Annotation hilft nicht: In const s: number | undefined = scores[name] verengt die Zuweisung s wieder auf number (B3.3). Prüfen Sie stattdessen zur Laufzeit, mit if (name in scores) oder s === undefined, oder schreiben Sie es in die Signatur: [name: string]: number | undefined. Die Option noUncheckedIndexedAccess „will add undefined to any un-declared field in the type“, also wird missing.toFixed(1) zu error TS18048. strict schließt sie nicht ein, und die Kurs-tsconfig setzt sie nicht.

Quellen

Zuletzt geprüft am 4. Oktober 2026