Die wichtigsten Regeln
- `strict: true`, immer. Dazu
noUncheckedIndexedAccessundnoImplicitOverride. - Typen ableiten lassen, wo es geht; Parameter und öffentliche Rückgaben beschriften.
- `unknown` statt `any`,
asnur im Notfall. - Daten von außen validieren (Zod und Co.), danach vertrauen.
- Unions und Literaltypen statt Strings und Flags: Zustände modellieren.
- Unmögliche Zustände unmöglich machen.
Unmögliche Zustände vermeiden
Schlecht: Flags, die sich widersprechen können.
interface AnfrageSchlecht {
laedt: boolean;
daten?: string[];
fehler?: string; // laedt true UND fehler gesetzt? Was gilt?
}Besser: Jeder Zustand ist eine eigene Variante.
type Anfrage =
| { zustand: "leer" }
| { zustand: "laedt" }
| { zustand: "fertig"; daten: string[] }
| { zustand: "fehler"; fehler: string };
function zeige(a: Anfrage): string {
switch (a.zustand) {
case "leer": return "Noch nichts geladen";
case "laedt": return "Lädt ...";
case "fertig": return `${a.daten.length} Einträge`;
case "fehler": return `Fehler: ${a.fehler}`;
}
}
console.log(zeige({ zustand: "laedt" }), "|", zeige({ zustand: "fertig", daten: ["a", "b"] }));Lädt ... | 2 Einträge
Der Compiler stellt sicher, dass daten nur im Zustand fertig existiert.
Branded Types: Verwechslungen verhindern
number und string sind oft zu allgemein. Eine Nutzer-ID soll nicht mit einer Bestell-ID verwechselt werden können:
type Marke<T, M extends string> = T & { readonly __marke: M };
type NutzerId = Marke<number, "NutzerId">;
type BestellId = Marke<number, "BestellId">;
const nutzerId = (n: number) => n as NutzerId;
const bestellId = (n: number) => n as BestellId;
function ladeNutzer(id: NutzerId): string { return `Nutzer ${id}`; }
const n = nutzerId(5);
const b = bestellId(5);
console.log(ladeNutzer(n));Nutzer 5
type Marke<T, M extends string> = T & { readonly __marke: M };
type NutzerId = Marke<number, "NutzerId">;
type BestellId = Marke<number, "BestellId">;
declare const b: BestellId;
function ladeNutzer(id: NutzerId): void {}
ladeNutzer(b);error TS2345: Argument of type 'BestellId' is not assignable to parameter of type 'NutzerId'.
Funktionen klein und rein halten
Reine Funktionen (gleiche Eingabe, gleiche Ausgabe, keine Nebenwirkungen) sind in TypeScript besonders gut verständlich, weil die Signatur schon fast alles sagt. Trenne Berechnung von Ein-/Ausgabe.
Immutable arbeiten
interface Zustand { readonly zaehler: number; readonly liste: readonly string[] }
function erhoehe(z: Zustand): Zustand {
return { ...z, zaehler: z.zaehler + 1 };
}
function hinzufuegen(z: Zustand, eintrag: string): Zustand {
return { ...z, liste: [...z.liste, eintrag] };
}
let z: Zustand = { zaehler: 0, liste: [] };
z = hinzufuegen(erhoehe(z), "A");
console.log(z);{ zaehler: 1, liste: [ 'A' ] }Fehlerbehandlung
- Wirf
Error-Objekte (oder Unterklassen), keine Strings catch (e: unknown)und mitinstanceofprüfen- Für erwartbare Fehler (Eingabe ungültig) eignet sich ein
Ergebnis-Typ statt Ausnahmen - Fange Fehler dort, wo du sie sinnvoll behandeln kannst
Struktur eines Projekts
mein-projekt/
├─ src/
│ ├─ index.ts Einstieg
│ ├─ typen.ts gemeinsame Typen
│ ├─ dienste/ Logik
│ └─ util/ kleine Helfer
├─ tests/
├─ package.json
├─ tsconfig.json
└─ eslint.config.js- Eine Datei, ein Zweck; Exporte klein halten
- Keine zirkulären Importe
- Gemeinsame Typen in eigene Dateien
Werkzeuge
| Werkzeug | Zweck |
|---|---|
tsc --noEmit | Typen prüfen (in CI) |
| typescript-eslint | zusätzliche Regeln (kein any, keine unnötigen Zusicherungen) |
| Prettier | Formatierung |
| Vitest / Jest | Tests (mit Typprüfung) |
| tsx, Vite, esbuild | schnell ausführen/bündeln |
| Zod | Laufzeit-Validierung |
Leistung der Typprüfung
Sehr komplexe Typen (verschachtelte bedingte Typen) verlangsamen den Compiler und die Editoren. Halte Typen einfach und verständlich. Wenn niemand sie lesen kann, helfen sie nicht.
Merke
- Immer
strict,unknownstattany, Daten von außen validieren - Zustände als Unions modellieren: unmögliche Zustände unmöglich machen
- Branded Types verhindern verwechselte IDs
- Reine Funktionen und unveränderliche Daten sind gut zu typisieren
- Werkzeuge:
tsc --noEmit, typescript-eslint, Prettier, Tests
Aufgabe
Modelliere einen Warenkorb-Zustand (leer, aktiv mit Artikeln, bezahlt mit Bestellnummer) als Union und schreibe eine Funktion, die jeden Zustand beschreibt.