Kurz: 1. strict: true, immer. Dazu noUncheckedIndexedAccess und noImplicitOverride. 2. Typen ableiten lassen, wo es geht; Parameter und öffentliche Rückgaben beschriften. 3. unknown statt any, as nur im Notfall. 4.
Teil des Kurses TypeScript
Kapitel 14 von 15 im Kurs TypeScript (Abschnitt „Praxis“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.
Die wichtigsten Regeln
strict: true, immer. DazunoUncheckedIndexedAccessundnoImplicitOverride.- Typen ableiten lassen, wo es geht; Parameter und öffentliche Rückgaben beschriften.
unknownstattany,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"] }));Ausgabe:
Lädt ... | 2 EinträgeDer 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));Ausgabe:
Nutzer 5type 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);Fehlermeldung:
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);Ausgabe:
{ 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
Übungsaufgabe
Modelliere einen Warenkorb-Zustand (leer, aktiv mit Artikeln, bezahlt mit Bestellnummer) als Union und schreibe eine Funktion, die jeden Zustand beschreibt.
Quiz zur Selbstkontrolle
Wie vermeidest du widersprüchliche Flags wie laedt und fehler?
- Als Union mit eigenem Zustand je Variante modellieren (richtig)
- Mehr Booleans einführen
- Alles optional machen
- any verwenden
Wofür sind Branded Types?
- Damit gleichartige Werte (z. B. verschiedene IDs) nicht verwechselt werden (richtig)
- Für Markennamen
- Für CSS-Klassen
- Für Konstanten
Was gehört in die CI?
- tsc —noEmit und Tests (richtig)
- Nur Formatierung
- Nichts
- Nur eslint ohne Typen
Weiter im Kurs
Zurück: Typen für Bibliotheken und Deklarationsdateien
Weiter: Nachschlagen: Spickzettel
Alle Kapitel: TypeScript im Überblick