Conditional Types
Lerne Conditional Types in TypeScript für bedingte Typ-Logik.
Aktualisiert:
Conditional Types
Conditional Types ermöglichen bedingte Logik auf Typ-Ebene. Sie folgen dem Muster T extends U ? X : Y - wenn T zu U zuweisbar ist, ist der Ergebnistyp X, sonst Y.
Grundlagen
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true (Literal ist string)
Mit Generics
type Check<T> = T extends string
? "It's a string"
: T extends number
? "It's a number"
: "Something else";
type R1 = Check<string>; // "It's a string"
type R2 = Check<number>; // "It's a number"
type R3 = Check<boolean>; // "Something else"
Distributive Conditional Types
Bei Union Types wird der Conditional Type auf jeden Member angewendet:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// string[] | number[] (nicht (string | number)[])
// Um Distribution zu verhindern, wrap in Tuple:
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDistributive<string | number>;
// (string | number)[]
infer Keyword
Mit infer kannst du Typen aus anderen Typen extrahieren:
// Extrahiere den Rückgabetyp einer Funktion
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "Max" };
}
type UserType = ReturnType<typeof getUser>;
// { id: number; name: string; }
Parameter-Typen extrahieren
// Alle Parameter
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number): string {
return `Hello ${name}, you are ${age}`;
}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number]
// Erster Parameter
type FirstParameter<T> = T extends (first: infer F, ...rest: any[]) => any
? F
: never;
type FirstParam = FirstParameter<typeof greet>;
// string
Array Element Type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Elem = ArrayElement<string[]>; // string
type Elem2 = ArrayElement<number[]>; // number
// Auch für Tuples
type First<T> = T extends [infer F, ...any[]] ? F : never;
type Last<T> = T extends [...any[], infer L] ? L : never;
type F = First<[1, 2, 3]>; // 1
type L = Last<[1, 2, 3]>; // 3
Promise unwrapping
type Awaited<T> = T extends Promise<infer U>
? Awaited<U> // Rekursiv für nested Promises
: T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<string>; // string
Built-in Conditional Types
TypeScript hat einige eingebaute Conditional Types:
Extract und Exclude
// Extract - behält nur Types die zu U zuweisbar sind
type Extract<T, U> = T extends U ? T : never;
// Exclude - entfernt Types die zu U zuweisbar sind
type Exclude<T, U> = T extends U ? never : T;
type T1 = Extract<string | number | boolean, string | number>;
// string | number
type T2 = Exclude<string | number | boolean, string>;
// number | boolean
// Praktisch für Union-Manipulation
type Events = "click" | "scroll" | "keydown" | "keyup";
type KeyEvents = Extract<Events, "keydown" | "keyup">;
// "keydown" | "keyup"
NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
type T1 = NonNullable<string | null | undefined>;
// string
Praktische Conditional Types
Flatten Type
type Flatten<T> = T extends Array<infer U> ? U : T;
type Flat1 = Flatten<string[]>; // string
type Flat2 = Flatten<number>; // number
type Flat3 = Flatten<(string | number)[]>; // string | number
// Deep Flatten
type DeepFlatten<T> = T extends Array<infer U>
? DeepFlatten<U>
: T;
type Deep = DeepFlatten<string[][][]>; // string
Function Type Manipulation
// Funktion mit anderem Rückgabetyp
type ChangeReturnType<F, R> = F extends (...args: infer A) => any
? (...args: A) => R
: never;
type Original = (x: number, y: string) => boolean;
type Changed = ChangeReturnType<Original, string>;
// (x: number, y: string) => string
// Async Version einer Funktion
type Async<F> = F extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: never;
type SyncFn = (x: number) => string;
type AsyncFn = Async<SyncFn>;
// (x: number) => Promise<string>
Object Property Types
// Extrahiere nur Properties eines bestimmten Typs
type PropertiesOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
type User = {
id: number;
name: string;
email: string;
age: number;
active: boolean;
};
type StringProps = PropertiesOfType<User, string>;
// "name" | "email"
type NumberProps = PropertiesOfType<User, number>;
// "id" | "age"
API Response Types
type ApiResponse<T> =
| { status: "success"; data: T }
| { status: "error"; error: string };
type UnwrapResponse<T> = T extends ApiResponse<infer U> ? U : never;
type UserResponse = ApiResponse<{ id: number; name: string }>;
type UserData = UnwrapResponse<UserResponse>;
// { id: number; name: string }
Event Handler Types
type EventMap = {
click: { x: number; y: number };
focus: { target: string };
submit: { data: FormData };
};
type EventHandler<K extends keyof EventMap> = (event: EventMap[K]) => void;
type EventHandlers = {
[K in keyof EventMap as `on${Capitalize<K>}`]: EventHandler<K>;
};
// {
// onClick: (event: { x: number; y: number }) => void;
// onFocus: (event: { target: string }) => void;
// onSubmit: (event: { data: FormData }) => void;
// }
Kombination mit Template Literals
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type Person = {
name: string;
age: number;
};
type PersonAccessors = Getters<Person> & Setters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// setName: (value: string) => void;
// setAge: (value: number) => void;
// }
Rekursive Conditional Types
// Deep Partial
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// Deep Required
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// JSON-serialisierbare Typen
type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
type ToJson<T> = T extends Date
? string
: T extends object
? { [K in keyof T]: ToJson<T[K]> }
: T;
Zusammenfassung
| Concept | Syntax | Verwendung |
|---|---|---|
| Basic | T extends U ? X : Y | Bedingte Typen |
| infer | T extends X<infer U> | Typ-Extraktion |
| Extract | Extract<T, U> | Behalte matchende |
| Exclude | Exclude<T, U> | Entferne matchende |
| Distribution | T extends any ? ... | Union-Iteration |
Best Practices:
- Nutze
inferfür Typ-Extraktion - Verstehe Distribution bei Unions
- Kombiniere mit Mapped Types für mächtige Transformationen
- Verwende rekursive Types mit Vorsicht (Komplexität)
Im nächsten Kapitel lernst du die eingebauten Utility Types!