EMZETT.
Login

Kurz: TypeScript bringt eingebaute Hilfstypen mit, die aus bestehenden Typen neue machen. Sie sparen viel Schreibarbeit.

Teil des Kurses TypeScript

Kapitel 9 von 15 im Kurs TypeScript. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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

TypErgebnis
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 }));

Ausgabe:

{ 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);

Ausgabe:

[ '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);

Ausgabe:

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);

Ausgabe:

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);

Ausgabe:

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);

Ausgabe:

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);

Ausgabe:

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

Ausgabe:

[ 1, 0, 0 ]
#00FF00

Merke

  • Partial, Required, Readonly, Pick, Omit, Record formen Objekttypen um
  • ReturnType, Parameters, Awaited leiten Typen aus Funktionen ab
  • typeof und keyof leiten Typen aus Werten und Schlüsseln ab
  • Mapped Types ([K in keyof T]) und bedingte Typen (T extends U ? X : Y) bauen eigene Hilfstypen
  • satisfies prüft gegen einen Typ, ohne den genauen Typ zu verlieren

Übungsaufgabe

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.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Generics

Weiter: Module und die tsconfig.json

Alle Kapitel: TypeScript im Überblick