TypeScript bringt eingebaute Hilfstypen mit, die aus bestehenden Typen neue machen. Sie sparen viel Schreibarbeit.
interface Nutzer {
id: number;
name: string;
email: string;
alter?: number;
}Die wichtigsten Utility-Typen
| Typ | Ergebnis |
|---|---|
Partial<T> | alle Eigenschaften optional |
Required<T> | alle Eigenschaften Pflicht |
Readonly<T> | alle Eigenschaften schreibgeschützt |
Pick<T, K> | nur die genannten Eigenschaften |
Omit<T, K> | alle außer den genannten |
Record<K, V> | Objekt mit Schlüsseln K und Werten V |
Exclude<U, X> | Union ohne X |
Extract<U, X> | Union nur mit X |
NonNullable<T> | ohne null/undefined |
ReturnType<F> | Rückgabetyp einer Funktion |
Parameters<F> | Parametertypen als Tupel |
Awaited<T> | Ergebnis eines Promise |
interface Nutzer { id: number; name: string; email: string; alter?: number }
type NutzerUpdate = Partial<Nutzer>; // für PATCH: nur geänderte Felder
type NutzerNeu = Omit<Nutzer, "id">; // vor dem Speichern gibt es keine id
type NutzerKarte = Pick<Nutzer, "id" | "name">;
type NurLesen = Readonly<Nutzer>;
const update: NutzerUpdate = { name: "Mia Neu" };
const neu: NutzerNeu = { name: "Tom", email: "tom@example.org" };
const karte: NutzerKarte = { id: 1, name: "Mia" };
console.log(update, neu, karte);
function aktualisiere(n: Nutzer, aenderung: Partial<Nutzer>): Nutzer {
return { ...n, ...aenderung };
}
console.log(aktualisiere({ id: 1, name: "Mia", email: "m@x.de" }, { alter: 18 }));{ name: 'Mia Neu' } { name: 'Tom', email: 'tom@example.org' } { id: 1, name: 'Mia' }
{ id: 1, name: 'Mia', email: 'm@x.de', alter: 18 }Record und Union-Hilfen
type Rolle = "admin" | "nutzer" | "gast";
const rechte: Record<Rolle, string[]> = {
admin: ["lesen", "schreiben", "löschen"],
nutzer: ["lesen", "schreiben"],
gast: ["lesen"],
};
type OhneGast = Exclude<Rolle, "gast">; // "admin" | "nutzer"
type NurAdmin = Extract<Rolle, "admin" | "root">; // "admin"
const r: OhneGast = "nutzer";
console.log(rechte[r], rechte.gast.length);[ 'lesen', 'schreiben' ] 1
Weil rechte ein Record<Rolle, ...> ist, muss jede Rolle vorkommen. Fehlt eine, meldet der Compiler es. Das ist ein guter Weg, Vollständigkeit zu erzwingen.
Typen aus Funktionen und Werten ableiten
function erzeugeNutzer(name: string, id = 1) {
return { id, name, aktiv: true, rollen: ["nutzer"] as string[] };
}
type NutzerErgebnis = ReturnType<typeof erzeugeNutzer>; // { id: number; name: string; aktiv: boolean; rollen: string[] }
type ErzeugerParameter = Parameters<typeof erzeugeNutzer>; // [name: string, id?: number]
const n: NutzerErgebnis = erzeugeNutzer("Mia");
const args: ErzeugerParameter = ["Tom", 2];
console.log(n.name, args);
async function ladeDaten() { return { zahl: 42, text: "ok" }; }
type Daten = Awaited<ReturnType<typeof ladeDaten>>; // { zahl: number; text: string }
const d: Daten = { zahl: 1, text: "x" };
console.log(d);Mia [ 'Tom', 2 ]
{ zahl: 1, text: 'x' }Das spart doppelte Typdeklarationen: Der Typ folgt immer dem tatsächlichen Code.
typeof, keyof und Indexzugriff
const konfig = { host: "localhost", port: 8080, debug: true } as const;
type Konfig = typeof konfig; // Typ aus dem Wert
type Schluessel = keyof Konfig; // "host" | "port" | "debug"
type PortTyp = Konfig["port"]; // 8080
type Werte = Konfig[keyof Konfig]; // "localhost" | 8080 | true
const s: Schluessel = "port";
const p: PortTyp = 8080;
console.log(s, p, konfig.host);port 8080 localhost
Mapped Types: eigene Hilfstypen
Ein Mapped Type baut aus einem Typ einen neuen, indem er alle Schlüssel durchläuft:
type Optional<T> = { [K in keyof T]?: T[K] }; // wie Partial
type Setter<T> = { [K in keyof T as `set${Capitalize<string & K>}`]: (wert: T[K]) => void };
interface Person { name: string; alter: number }
type PersonSetter = Setter<Person>; // { setName: (w: string) => void; setAlter: (w: number) => void }
const setter: PersonSetter = {
setName: (w) => console.log("Name:", w),
setAlter: (w) => console.log("Alter:", w),
};
setter.setName("Mia");
setter.setAlter(17);Name: Mia Alter: 17
Bedingte Typen
Mit T extends U ? X : Y wählst du den Typ abhängig von einer Bedingung:
type IstArray<T> = T extends unknown[] ? "ja" : "nein";
type Element<T> = T extends (infer E)[] ? E : T; // infer: Typ herausziehen
const a: IstArray<number[]> = "ja";
const b: IstArray<string> = "nein";
const e: Element<string[]> = "text";
const f: Element<number> = 5;
console.log(a, b, e, f);ja nein text 5
Bedingte Typen sind das Handwerkszeug für Bibliotheksautoren. Im Alltag reichen meist die eingebauten Utility-Typen.
Template-Literal-Typen
Textmuster auf Typebene:
type Methode = "get" | "post";
type Pfad = "/nutzer" | "/beitraege";
type Route = `${Uppercase<Methode>} ${Pfad}`;
const r: Route = "GET /nutzer";
console.log(r);GET /nutzer
satisfies
satisfies prüft, ob ein Wert einem Typ genügt, ohne den genaueren Typ zu verlieren:
type Farben = Record<string, string | number[]>;
const palette = {
rot: [255, 0, 0],
gruen: "#00ff00",
} satisfies Farben;
console.log(palette.rot.map((x) => x / 255)); // bleibt number[]
console.log(palette.gruen.toUpperCase()); // bleibt string[ 1, 0, 0 ] #00FF00
Merke
Partial,Required,Readonly,Pick,Omit,Recordformen Objekttypen umReturnType,Parameters,Awaitedleiten Typen aus Funktionen abtypeofundkeyofleiten Typen aus Werten und Schlüsseln ab- Mapped Types (
[K in keyof T]) und bedingte Typen (T extends U ? X : Y) bauen eigene Hilfstypen satisfiesprüft gegen einen Typ, ohne den genauen Typ zu verlieren
Aufgabe
Gegeben ist ein Interface Produkt. Erzeuge daraus mit Utility-Typen: einen Typ für das Anlegen (ohne id), einen für Updates (alles optional) und einen Typ nur mit name und preis.