Conditional 1. Conditional Types — T extends U ? X : Y
Conditional Types sind das mächtigste Werkzeug im TypeScript-Typsystem. Sie erlauben es, Typen abhängig von Bedingungen zu berechnen — ähnlich wie ein ternärer Operator, aber auf Typ-Ebene. Claude Code nutzt sie täglich, um präzise API-Typen zu generieren.
Die Grundform lautet: T extends U ? X : Y. Ist T assignierbar zu U, ergibt der Typ X, sonst Y.
// Grundform: T extends U ? X : Y type IsString<T> = T extends string ? "yes" : "no"; type A = IsString<string>; // "yes" type B = IsString<number>; // "no" // NonNullable selbst implementieren type MyNonNullable<T> = T extends null | undefined ? never : T; type C = MyNonNullable<string | null>; // string type D = MyNonNullable<number | undefined>; // number
Das infer-Schlüsselwort
infer erlaubt es, einen Typ innerhalb einer Conditional Type zu "extrahieren" und als Variable zu binden. Damit lassen sich komplexe Typen wie Rückgabetypen oder Promise-Werte herauslösen.
// infer: Typ-Extraktion in Conditional Types type UnwrapPromise<T> = T extends Promise<infer U> ? U : T; type E = UnwrapPromise<Promise<string>>; // string type F = UnwrapPromise<number>; // number (kein Promise) // Erstes Element eines Tuple-Typs extrahieren type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never; type G = Head<[string, number, boolean]>; // string // Letztes Element (rekursiv) type Last<T extends any[]> = T extends [...any[], infer L] ? L : never; type H = Last<[string, number, boolean]>; // boolean
Distributive Conditional Types
Wenn T ein Union-Typ ist, wird der Conditional Type über jeden Member verteilt. Dieses Verhalten heißt "Distributivität" und ist oft sehr nützlich — kann aber auch überraschend sein.
// Distributiv: wird über Union-Member verteilt type ToArray<T> = T extends any ? T[] : never; type I = ToArray<string | number>; // Ergebnis: string[] | number[] (distributiv!) // NICHT: (string | number)[] // Distributivität deaktivieren: in Tuple einwickeln type ToArrayNonDist<T> = [T] extends [any] ? T[] : never; type J = ToArrayNonDist<string | number>; // Ergebnis: (string | number)[] // Praktisch: Union-Member filtern type Filter<T, U> = T extends U ? T : never; type OnlyStrings = Filter<string | number | boolean, string>; // Ergebnis: string
never in Conditional Types lassen sich Union-Member gezielt herausfiltern. Das ist die Basis für viele eingebaute Utility Types wie Extract<T, U> und Exclude<T, U>.
Mapped 2. Mapped Types — Readonly, Partial, Pick selbst bauen
Mapped Types transformieren jeden Key eines bestehenden Typs nach einer Regel. Die Syntax { [K in keyof T]: ... } ist das Herzstück aller eingebauten Utility Types in TypeScript. Wer sie versteht, kann jedes Framework-Typsystem lesen und erweitern.
// Grundform: alle Properties transformieren type Stringify<T> = { [K in keyof T]: string; }; interface User { id: number; name: string; active: boolean; } type StringUser = Stringify<User>; // { id: string; name: string; active: string; } // Readonly selbst bauen type MyReadonly<T> = { readonly [K in keyof T]: T[K]; }; // Partial selbst bauen type MyPartial<T> = { [K in keyof T]?: T[K]; }; // Required selbst bauen (-? entfernt optional) type MyRequired<T> = { [K in keyof T]-?: T[K]; };
// Pick selbst bauen: nur bestimmte Keys behalten type MyPick<T, K extends keyof T> = { [P in K]: T[P]; }; type UserPreview = MyPick<User, "id" | "name">; // { id: number; name: string; } // Record selbst bauen type MyRecord<K extends keyof any, V> = { [P in K]: V; }; type Roles = MyRecord<"admin" | "user" | "guest", boolean>; // { admin: boolean; user: boolean; guest: boolean; } // Key-Remapping mit as (TypeScript 4.1+) type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; type UserGetters = Getters<User>; // { getId(): number; getName(): string; getActive(): boolean; }
as und Template Literal Types zusammen erlauben es, ganze API-Interfaces aus einem einzigen Interface zu generieren — z.B. Getter/Setter-Paare oder Event-Handler-Namen.
Template 3. Template Literal Types — EventName, Route-Pattern, CSS
Mit TypeScript 4.1 wurden Template Literal Types eingeführt. Sie erlauben es, Strings auf Typ-Ebene zu konstruieren und zu manipulieren — ein Game-Changer für typsichere Event-Systeme, API-Routen und CSS-Properties.
// Grundform: String-Kombinationen type Direction = "top" | "right" | "bottom" | "left"; type MarginProperty = `margin-${Direction}`; // "margin-top" | "margin-right" | "margin-bottom" | "margin-left" // EventName-Pattern type EventName<T extends string> = `on${Capitalize<T>}`; type ClickEvent = EventName<"click">; // "onClick" type ChangeEvent = EventName<"change">; // "onChange" // Auto-generierte Event-Handler aus Interface type Events = "click" | "focus" | "blur" | "submit"; type Handlers = { [E in Events as `on${Capitalize<E>}`]: (event: Event) => void; }; // { onClick, onFocus, onBlur, onSubmit }
// Route-Pattern-Types — typsichere API-Routen type RouteParam<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? Param | RouteParam<`/${Rest}`> : T extends `${string}:${infer Param}` ? Param : never; type Params = RouteParam<"/users/:userId/posts/:postId">; // "userId" | "postId" // String-Manipulationen mit eingebauten Utility Types type Upper = Uppercase<"hello">; // "HELLO" type Lower = Lowercase<"WORLD">; // "world" type Cap = Capitalize<"typescript">; // "Typescript" type UnCap = Uncapitalize<"TypeScript">; // "typeScript" // CSS-Property-Types type CSSValue = string | number; type CSSProp = "color" | "background" | "padding" | "margin"; type CSSVarName<T extends string> = `--${T}`; type CSSVars = CSSVarName<CSSProp>; // "--color" | "--background" | "--padding" | "--margin"
Utility 4. Utility Types tief verstehen — ReturnType, Awaited, ConstructorParameters
TypeScripts eingebaute Utility Types sind oft bekannt, aber selten vollständig verstanden. Wer weiß, wie sie intern funktionieren, kann sie kombinieren, erweitern und eigene Type-Bibliotheken bauen.
// ReturnType — Rückgabetyp einer Funktion extrahieren function createUser(name: string, age: number) { return { id: Math.random(), name, age, createdAt: new Date() }; } type CreatedUser = ReturnType<typeof createUser>; // { id: number; name: string; age: number; createdAt: Date; } // Parameters — Parameter-Typen als Tuple extrahieren type CreateUserParams = Parameters<typeof createUser>; // [name: string, age: number] // InstanceType — Typ einer Klassen-Instanz class ApiClient { constructor(private baseUrl: string) {} get<T>(path: string): Promise<T> { return fetch(this.baseUrl + path).then(r => r.json()); } } type Client = InstanceType<typeof ApiClient>; // ApiClient (Instanz-Typ) // ConstructorParameters — Konstruktor-Parameter extrahieren type ClientArgs = ConstructorParameters<typeof ApiClient>; // [baseUrl: string]
// Awaited — rekursiv Promises auspacken (TS 4.5+) type A = Awaited<Promise<string>>; // string type B = Awaited<Promise<Promise<number>>>; // number type C = Awaited<boolean | Promise<string>>; // boolean | string // Praktisch: async-Funktions-Rückgabetyp async function fetchUsers(): Promise<User[]> { return []; } type FetchResult = Awaited<ReturnType<typeof fetchUsers>>; // User[] (Promise wurde ausgepackt) // Omit, Pick, Exclude, Extract kombinieren type PublicUser = Omit<User, "password" | "secretKey">; type StringKeys = Extract<keyof User, string>; type NoActive = Exclude<keyof User, "active">;
Guard 5. Discriminated Unions und Type Guards
Discriminated Unions sind das bevorzugte Pattern in TypeScript, um mit verschiedenen Zuständen oder Varianten typsicher umzugehen. In Kombination mit Type Guards und dem never-Typ für Exhaustiveness-Checking entstehen robuste Zustandsmaschinen.
// Discriminated Union: gemeinsames Discriminant-Feld type LoadingState = { status: "loading" }; type SuccessState = { status: "success"; data: User[] }; type ErrorState = { status: "error"; message: string }; type AsyncState = LoadingState | SuccessState | ErrorState; // Exhaustiveness-Checking mit never function assertNever(x: never): never { throw new Error(`Unhandled case: ${JSON.stringify(x)}`); } function renderState(state: AsyncState): string { switch (state.status) { case "loading": return "Laden..."; case "success": return `${state.data.length} Nutzer geladen`; case "error": return `Fehler: ${state.message}`; default: return assertNever(state); // Compile-Fehler wenn Case fehlt! } }
// User-Defined Type Guard mit "is" function isUser(value: unknown): value is User { return ( typeof value === "object" && value !== null && "id" in value && "name" in value ); } // instanceof Guard function handleError(err: unknown): string { if (err instanceof Error) return err.message; if (typeof err === "string") return err; return "Unbekannter Fehler"; } // "in"-Operator als Type Guard type Cat = { meow(): void }; type Dog = { bark(): void }; function speak(animal: Cat | Dog) { if ("meow" in animal) { animal.meow(); // TypeScript kennt jetzt: Cat } else { animal.bark(); // TypeScript kennt jetzt: Dog } } // Assertion Function (TS 3.7+) function assertIsString(val: unknown): asserts val is string { if (typeof val !== "string") throw new Error("Not a string!"); }
never kommen dem sehr nahe — besonders in Kombination mit dem neuen satisfies-Operator (TS 4.9+).
Infer 6. Infer und rekursive Types — DeepPartial, Flatten, UnpackPromise
Rekursive Typen und infer zusammen sind das fortgeschrittenste Werkzeug im TypeScript-Arsenal. Sie erlauben es, beliebig tief verschachtelte Datenstrukturen auf Typ-Ebene zu transformieren — ohne Runtime-Overhead.
// DeepPartial — alle Properties rekursiv optional type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; interface Config { server: { host: string; port: number; ssl: boolean }; db: { url: string; pool: { min: number; max: number } }; } type PartialConfig = DeepPartial<Config>; // Alle verschachtelten Felder optional — perfekt für Overrides // DeepReadonly — alle Properties rekursiv readonly type DeepReadonly<T> = T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
// Flatten — verschachtelte Arrays auspacken type Flatten<T> = T extends Array<infer Item> ? Flatten<Item> : T; type F1 = Flatten<string[][]>; // string type F2 = Flatten<number[][][]>; // number type F3 = Flatten<boolean>; // boolean (kein Array) // UnpackPromise — tief verschachtelte Promises auspacken type UnpackPromise<T> = T extends Promise<infer U> ? UnpackPromise<U> : T; type P1 = UnpackPromise<Promise<Promise<string>>>; // string // Rekursiver Mapped Type: alle Werte stringifizieren type DeepStringify<T> = { [K in keyof T]: T[K] extends object ? DeepStringify<T[K]> : string; };
// Tuple-Länge zur Compile-Zeit type TupleLength<T extends any[]> = T["length"]; type Len = TupleLength<[string, number, boolean]>; // 3 // Alle Keys eines Union-Typs type UnionKeys<T> = T extends any ? keyof T : never; type K = UnionKeys<Cat | Dog>; // "meow" | "bark" // OverloadedReturnType — letzten Overload auflösen type OverloadedReturnType<T> = T extends { (...args: any[]): infer R; (...args: any[]): infer R; (...args: any[]): infer R; (...args: any[]): infer R; } ? R : never; // Variance: Covariant vs Contravariant type Covariant<T> = () => T; // T ist covariant (Ausgabe) type Contravariant<T> = (x: T) => void; // T ist contravariant (Eingabe)
type-fest als Battle-tested Utility-Library.
TypeScript Advanced Types mit Claude Code meistern
Claude Code analysiert dein Typsystem, schlägt bessere Generic-Strukturen vor und hilft dir, Type-Fehler in Sekunden zu verstehen. Jetzt 14 Tage kostenlos testen.
Kostenlos starten →