Start / Cheat Sheets

Cheat Sheet

TypeScript 7 Typsystem — Cheat Sheet

Stand: · Modern TypeScript 7

TypeScriptTypsystemGenericsUnion Types

Zum Danebenlegen, während man modelliert: Welcher Baustein sagt was, welche Prüfung verengt worauf, wo hört der Compiler auf zu helfen. Die zweite Hälfte des Seminars — Toolchain, Migration, Coding Agents — steht auf dem Blatt TypeScript 7 im Projekt.

Maßgeblich bleibt die TypeScript-Dokumentation; dieses Blatt ist eine Auswahl, keine Referenz. Wie man die Entscheidungen trifft, ist die Seminarleistung — hier steht nur, wie sie heißen.

Compile-Time und Laufzeit

Ein Typ beschreibt eine Absicht und erzwingt zur Laufzeit nichts. Beim Übersetzen verschwindet er restlos (Type Erasure) — das ist die Aussage, an der alles Weitere hängt.

EbeneLegt festWird durchgesetzt von
ECMAScriptWas die Sprache können mussdem Standardisierungsgremium
JavaScriptWas zur Laufzeit passiertder Engine im Browser oder in Node
TypeScriptWas der Code behaupten darfdem Compiler, vor der Auslieferung

Kein Typ überlebt die Netzwerkgrenze, den Dateizugriff oder das Formular. Wo Daten von außen kommen, ist der Typ eine Behauptung; geprüft wird separat (siehe „Die Grenze zu externen Daten”).

Modul: TypeScript 7 im JavaScript-Ökosystem

Grundtypen: welcher Baustein sagt was

BausteinAussageWann falsch gewählt
Arraybeliebig viele, alle gleichwenn die Anzahl fachlich feststeht
Tupelfeste Anzahl, feste Bedeutung je Stellewenn die Liste wachsen darf
Objekttypbekannte Schlüsselwenn die Schlüssel erst zur Laufzeit entstehen
Index-Signaturbeliebige Schlüssel, ein Werttypwenn die Schlüssel bekannt sind
type Preise = number[];                        // beliebig viele
type Zeitraum = [von: string, bis: string];    // genau zwei, benannt

Benannte Tupelstellen kosten nichts und ersparen die Rückfrage, welcher der beiden Werte der Beginn ist.

Modul: Typinferenz und sichere Grundtypen

any, unknown, never, void

TypBedeutetRichtig eingesetzt bei
anyprüf hier nichts mehrfast nie, allenfalls als Zwischenschritt
unknownirgendetwas, erst prüfenallen Daten von außerhalb des Programms
neverkann nicht eintretenExhaustiveness-Prüfungen
voidgibt nichts zurückRückrufen, deren Ergebnis egal ist

unknown lässt sich nicht versehentlich benutzen, nur bewusst prüfen — der Weg zum Domänentyp führt zwangsläufig durch eine Prüfstelle, und die ist im Diff sichtbar. any breitet sich dagegen aus: Was es berührt, wird ebenfalls ungeprüft.

Modul: Typinferenz und sichere Grundtypen

Was strict wirklich einschließt

Optionin strict?Was sie zusätzlich prüft
strictNullChecksjanull und undefined als eigene Fälle
noImplicitAnyjakeine stillen any-Parameter
exactOptionalPropertyTypesneinfehlend ist nicht dasselbe wie undefined
noUncheckedIndexedAccessneinIndexzugriffe können ins Leere greifen

Seit TypeScript 6.0 ist strict voreingestellt — die beiden unteren Optionen bleiben trotzdem einzeln zu setzen. „Wir sind strict” ist deshalb keine Angabe, solange nicht dazugesagt wird, welche Strenge gemeint ist.

Drei Schreibweisen, drei verschiedene Aussagen:

interface Auftrag {
  readonly nummer: string;         // da, nie änderbar
  abholnotiz?: string;             // darf ganz fehlen
  betragCent: number | undefined;  // da, evtl. leer
}

readonly schützt die Eigenschaft, nicht das Objekt — und gilt beim Übersetzen, nicht zur Laufzeit.

Modul: Typinferenz und sichere Grundtypen

Structural Typing, Interface oder Type Alias

FrageInterfaceType Alias
Objektformen beschreibenjaja
Unions, Tupel, Bedingtesneinja
Nachträglich erweiterbarja, offennein, geschlossen
Empfehlung im Seminaröffentliche Objektformenalles Übrige

Ein Type Alias auf string erzeugt keinen eigenen Typ, sondern nur einen zweiten Namen: Auftragsnummer und Kundennummer bleiben austauschbar, bis eine Marke dazukommt (siehe „Branded Types”).

Modul: Typinferenz und sichere Grundtypen

as const und satisfies

const orte = ["Talbrück", "Hafenrand"] as const;
type Filiale = (typeof orte)[number];       // Union aus der Liste

const werkstatt = {
  Talbrück: { plaetze: 6 },
  Hafenrand: { plaetze: 3 },
} satisfies Record<Filiale, { plaetze: number }>;
werkstatt.Talbrück.plaetze;                 // eng, kein Record

as const macht eine Liste zur Typquelle — Wert und Typ können nicht mehr auseinanderlaufen. satisfies prüft gegen einen Zieltyp, ohne die genauen Schlüssel zu verlieren; eine Annotation an derselben Stelle täte das. Beides wirkt beim Übersetzen, nicht zur Laufzeit. Und: Annotation und satisfies gleichzeitig gesetzt — die Annotation gewinnt.

Modul: Typinferenz und sichere Grundtypen

Narrowing: welche Prüfung verengt worauf

PrüfungVerengt aufGrenze
typeofprimitive Typenkennt keine Objektformen
instanceofKlassen und ihre Instanzenscheitert über Realm-Grenzen
inObjekte mit dieser Eigenschaftprüft Vorhandensein, nicht den Typ
Vergleich auf Literalgenau diesen Fallbraucht ein Unterscheidungsfeld

Ein eigenes Typprädikat (a is Freigegeben) lohnt, wenn die Erkennung mehrere Bedingungen braucht, an vielen Stellen gebraucht wird oder einen fachlichen Namen verdient. Seit TypeScript 5.5 leitet der Compiler Prädikate selbst ab — aber nur, wenn die Funktion direkt einen Vergleich zurückgibt.

Modul: Union Types und Control Flow Analysis

Discriminated Unions und Vollständigkeit

type Auftrag =
  | { zustand: "angenommen"; nummer: Auftragsnummer }
  | { zustand: "voranschlagOffen"; cent: number }
  | { zustand: "freigegeben"; cent: number }
  | { zustand: "abgeholt"; belegNr: string };

function text(a: Auftrag): string {
  switch (a.zustand) {
    case "angenommen": return "in Annahme";
    // …
    default: return nieErreicht(a);
  }
}
function nieErreicht(x: never): never { throw x; }

Der Gewinn: Ein angenommener Auftrag hat keinen Betrag — nicht undefined, sondern gar nicht. Kein Feld muss optional sein, nur weil es nicht überall vorkommt. Die never-Zeile macht daraus einen Vollständigkeitsbeweis: Ein neuer Zustand bricht beim Übersetzen, nicht im Betrieb.

Schalter gegen Zustand — der Umbau, der sich am häufigsten lohnt:

FrageBoolesche SchalterZustandstyp
Darstellbare Kombinationenacht bei drei Felderngenau die gültigen
Unmöglicher Zustandjederzeit erzeugbarnicht hinschreibbar
Neuer Fallein weiterer Schalter, 16 Fälleein weiteres Glied
Lesbarkeit im ReviewKombinationen im Kopfam Namen ablesbar

Erkennungszeichen im Bestand: zwei Schalter, die nie gleichzeitig wahr sein dürfen, und Kommentare der Sorte „nur gesetzt wenn”.

Modul: Union Types und Control Flow Analysis

Ausnahme oder Ergebniswert

SituationAusnahmeErgebniswert
Fachlich erwartbarer Fallneinja
Programmierfehler, Zustandsbruchjanein
Fehler soll durchgereicht werdenjaumständlich
Aufrufer muss reagierenleicht vergessenerzwungen
type Ergebnis<T> =
  | { ok: true; wert: T }
  | { ok: false; fehler: string };

Modul: Union Types und Control Flow Analysis

Generics: keyof, typeof, Indexed Access

OperatorNimmtLiefert
keyofeinen Typdie Union seiner Schlüssel
typeofeinen Wertdessen Typ
T[“feld”]Typ und Schlüsselden Typ dieser Eigenschaft
T[keyof T]einen Typdie Union aller Werttypen

Ein Typparameter, der nur an einer Stelle vorkommt, ist in Wahrheit any. Ein Constraint nennt genau das, was der Rumpf braucht — nicht mehr (schließt gültige Aufrufer aus) und nicht weniger (zwingt zu Prüfungen im Rumpf).

function nummer<T extends { nummer: string }>(x: T) { return x.nummer; }
type Seite<T = Auftrag> = { treffer: T[] };
type Wert<T> = T extends Ergebnis<infer W> ? W : never;
type Entwurf<T> = { [K in keyof T]?: T[K] };
type Ereignis = `auftrag:${"offen" | "fertig"}`;

Modul: Generics und wiederverwendbare APIs

Utility Types

Utility TypeMachtTypischer Einsatz
Partialalle Eigenschaften optionalEntwürfe und Teilaktualisierungen
Pick, OmitAuswahl aus einem TypSichten für eine Schnittstelle
RecordSchlüssel auf WerttypKonfigurationen
Readonlyalles schreibgeschütztWerte, die nicht mutiert werden
ReturnTypeRückgabe einer FunktionAbleiten statt Wiederholen

Ein eigener Utility Type lohnt ab der dritten Verwendung — und dann nur mit einem Namen, der seine Absicht verrät. Dass er kürzer aussieht als die Standardvariante, ist kein Grund.

Modul: Generics und wiederverwendbare APIs

Branded Types

declare const marke: unique symbol;
type Marke<T, N> = T & { readonly [marke]: N };
type Auftragsnummer = Marke<string, "Auftrag">;
type Kundennummer = Marke<string, "Kunde">;

const k = "K-8842" as Kundennummer;
const a: Auftragsnummer = k;   // jetzt ein Fehler

Lohnt bei Kennungen, die zur Laufzeit gleich aussehen und nie vertauscht werden dürfen; bei Werten mit Einheit (Cent gegen Euro, Sekunden gegen Millisekunden); und für geprüfte Daten — ein geprüfter Wert bekommt die Marke, ein ungeprüfter nicht. Die Marke existiert nur beim Übersetzen; zur Laufzeit bleibt es eine Zeichenkette.

Modul: Generics und wiederverwendbare APIs

Die Grenze zu externen Daten

QuelleTypisches FormatWas regelmäßig überrascht
API-AntwortJSONFeld fehlt, null statt Wert, Zahl als Text
FormularZeichenkettenalles ist Text, auch Zahlen und Datumsangaben
KonfigurationUmgebung, Dateileere Zeichenkette gilt als gesetzt
Datei-ImportJSON, CSVKodierung, Trennzeichen, fehlende Spalten
// Vorsicht: json() liefert any, nicht unknown
const roh: unknown = await antwort.json();
const teil = zuTeil(roh);   // prüft und wandelt um

Die Annotation auf unknown ist der ganze Trick — sie macht aus einem bequemen any eine erzwungene Entscheidung. Ein Ort für die Prüfung, nicht fünf.

Modul: Runtime-Sicherheit und externe Daten

Von Hand oder mit Schema

KriteriumVon HandSchema-Bibliothek
Kleines Modelltransparent, angemessenOverhead ohne Gewinn
Großes Modellwächst schlecht, fehleranfälligträgt
Typ und Prüfungzwei Quellen, laufen auseinandereine Quelle
Fehlermeldungselbst zu bauenmitgeliefert, mit Pfadangabe
Abhängigkeitkeineeine, mit eigener Fehlerkultur

Bei type-first muss jemand daran denken, Typ und Prüfung gemeinsam zu ändern; bei schema-first gibt es nur eine Seite, der Typ wird abgeleitet. Was Generierung aus OpenAPI oder JSON Schema dabei leistet — und was nicht:

ErwartungTrifft zuTrifft nicht zu
Typ passt zur Beschreibungjawenn die Beschreibung falsch ist
Typ passt zu den echten DatenneinBeschreibung ist kein Vertrag
Prüfung entfälltneinType Erasure gilt unverändert
Änderung fällt aufja, im Diffnur wenn neu generiert wird

Für Antworten eines Sprachmodells gilt nichts anderes: Ein angefordertes Schema ist nicht garantiert eingehalten, und dass die Antwort im eigenen Prozess entstanden ist, ändert an Type Erasure nichts.

Modul: Runtime-Sicherheit und externe Daten

Typische Fallen

  • Erwarten, dass eine Typannotation eine API-Antwort validiert. Sie behauptet nur; geprüft wird an der Grenze.
  • any einsetzen, um eine Fehlermeldung loszuwerden. Sie kommt später wieder, dann im Betrieb.
  • unknown mit einer Assertion in den Zieltyp zwingen statt zu prüfen.
  • json() ohne Annotation verwenden — das Ergebnis ist any, nicht unknown.
  • typeof null ist object. Der klassische Fallstrick, der bis heute trägt.
  • Verengung nach await oder in einer Zwischenvariablen verloren. Der Compiler verfolgt sie dort nicht mehr.
  • Das Unterscheidungsfeld als string typisieren statt als Literal — dann verengt nichts mehr.
  • Sich auf die Reihenfolge der Union-Glieder verlassen, etwa in Snapshot-Tests. Sie war nie zugesichert und ist in TypeScript 7 anders.
  • readonly für Laufzeitschutz halten und auf Kopien verzichten.
  • Optional Chaining überall streuen und den Fehlerfall damit unsichtbar machen.
  • Index-Signaturen einsetzen, obwohl die Schlüssel bekannt sind.
  • Conditional Types verschachteln, bis die Fehlermeldung unlesbar wird — spätestens dann liest sie im Review niemand mehr.

Zum Seminar Modern TypeScript 7