Zum Inhalt springen
TypeScript Fortgeschritten 30 min

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

ConceptSyntaxVerwendung
BasicT extends U ? X : YBedingte Typen
inferT extends X<infer U>Typ-Extraktion
ExtractExtract<T, U>Behalte matchende
ExcludeExclude<T, U>Entferne matchende
DistributionT extends any ? ...Union-Iteration

Best Practices:

  • Nutze infer fü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!

Zurück zum TypeScript Kurs