TypeScript Generics

6 Kernkonzepte
Die wichtigsten Konzepte für Generics in TypeScript: <T> · extends · infer · keyof · Utility Types · Generic Constraints Generics ermöglichen die Erstellung von wiederverwendbaren, typsicheren Komponenten. Sie sind das Herzstück von TypeScripts Typsystem und essenziell für die Entwicklung robuster Anwendungen.

Generic Functions – Typisierte Funktionen

function<T>(value: T): T
function identity<T>(value: T): T { return value; } const result = identity<string>("Hallo"); // Typ: string

Generic Functions erlauben es, Funktionen zu schreiben, die mit verschiedenen Typen arbeiten können, ohne die Typsicherheit zu verlieren. Der Typ wird beim Aufruf festgelegt.

Beispiele
// Grundlegende Generic Function
function wrapInArray<T>(value: T): T[] {
return [value];
}
const numbers = wrapInArray(42); // number[]
const strings = wrapInArray("Hallo"); // string[]
// Arrow Function mit Generics
const getValue = <T, U>(obj: T, key: keyof T): T[keyof T] => {
return obj[key];
};
const user = { name: "Anna", age: 30 };
const name = getValue(user, "name"); // string
// Mehrere Generic-Parameter
function zip<T, U>(arr1: T[], arr2: U[]): [T, U][] {
return arr1.map((item, index) => [item, arr2[index]]);
}
const zipped = zip([1, 2], ["a", "b"]); // [number, string][]
Tipp: TypeScript leitet Generic-Typen oft automatisch ab – Sie müssen den Typ nicht immer explizit angeben. Verwenden Sie explizite Typen, wenn die Inferenz nicht den gewünschten Typ ergibt.

Generic Interfaces – Typisierte Strukturen

interface Box<T> { value: T }
interface Box<T> { value: T; label: string; } const box: Box<number> = { value: 42, label: "Zahl" };

Generic Interfaces definieren Strukturen, die mit verschiedenen Typen parametrisiert werden können. Sie sind ideal für Container, Wrapper und Datenstrukturen.

Beispiele
// Generic Interface für API-Responses
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Verwendung mit verschiedenen Typen
const userResponse: ApiResponse<{ id: number; name: string }> = {
data: { id: 1, name: "Anna" },
status: 200,
message: "Erfolg"
};
// Generic Interface mit mehreren Typen
interface Pair<K, V> {
key: K;
value: V;
}
const pair: Pair<string, number> = { key: "age", value: 30 };
// Verschachtelte Generics
interface NestedBox<T> {
inner: Box<T>;
outer: string;
}
Tipp: Generic Interfaces sind besonders nützlich für Bibliotheken und Frameworks, da sie es ermöglichen, flexible und wiederverwendbare APIs zu definieren.

Generic Classes – Typisierte Klassen

class Stack<T> { push(item: T): void }
class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } } const numberStack = new Stack<number>();

Generic Classes ermöglichen die Erstellung von Klassen, die mit verschiedenen Typen arbeiten können – wie Container, Collections oder Datenstrukturen.

Beispiele
// Generic Klasse für eine Queue
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void { this.items.push(item); }
dequeue(): T | undefined { return this.items.shift(); }
peek(): T | undefined { return this.items[0]; }
isEmpty(): boolean { return this.items.length === 0; }
}
// Verwendung
const stringQueue = new Queue<string>();
stringQueue.enqueue("erste");
stringQueue.enqueue("zweite");
console.log(stringQueue.dequeue()); // "erste"
// Generic Klasse mit mehreren Typ-Parametern
class Dictionary<K, V> {
private items: Map<K, V> = new Map();
set(key: K, value: V): void { this.items.set(key, value); }
get(key: K): V | undefined { return this.items.get(key); }
has(key: K): boolean { return this.items.has(key); }
}
Tipp: Generic Classes sind ideal für Datenstrukturen wie Stacks, Queues, Trees oder für Repository-Patterns in der Backend-Entwicklung.

Constraints – Generics einschränken

extends · keyof · typeof
function getLength<T extends { length: number }>(item: T): number { return item.length; } getLength("Hallo"); // 5 getLength([1, 2, 3]); // 3

Constraints schränken Generic-Typen ein – sie definieren, welche Eigenschaften oder Methoden ein Typ haben muss. Das wird mit dem Schlüsselwort extends erreicht.

Beispiele
// Constraint: Objekt mit id-Eigenschaft
interface HasId { id: number; }
function getById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// keyof Constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Anna", age: 30 };
const name = getProperty(person, "name"); // string
// Mehrere Constraints
interface HasName { name: string; }
interface HasAge { age: number; }
function printPerson<T extends HasName & HasAge>(person: T): void {
console.log(`${person.name} (${person.age})`);
}
// typeof Constraint
function createInstance<T extends { new(): T }>(ctor: T): T {
return new ctor();
}
Tipp: Constraints sind essenziell, um auf spezifische Eigenschaften oder Methoden von Generic-Typen zuzugreifen. Sie machen den Code sicherer und lesbarer.

infer – Typen extrahieren

infer R · ReturnType · Parameters
type ArrayElement<T> = T extends (infer U)[] ? U : never; type Element = ArrayElement<string[]>; // string

infer wird in Conditional Types verwendet, um Typen zu extrahieren – z.B. den Elementtyp eines Arrays oder den Rückgabetyp einer Funktion.

Beispiele
// ReturnType (Rückgabetyp einer Funktion)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
const fn = (x: number) => x + 1;
type Result = MyReturnType<typeof fn>; // number
// Parameters (Parameter-Typen einer Funktion)
type MyParameters<T> = T extends (...infer P) => any ? P : never;
type Params = MyParameters<(a: string, b: number) => void>; // [string, number]
// Promise-Typ extrahieren
type PromiseValue<T> = T extends Promise<infer U> ? U : T;
type Value = PromiseValue<Promise<string>>; // string
// Verschachteltes infer
type DeepPromiseValue<T> = T extends Promise<infer U> ? DeepPromiseValue<U> : T;
type DeepValue = DeepPromiseValue<Promise<Promise<number>>>; // number
Tipp: infer ist ein mächtiges Werkzeug für fortgeschrittene Typmanipulationen. Es wird oft in Bibliotheken verwendet, um Typen von generischen Komponenten zu extrahieren.

Generic Utility Types – Eingebaute Hilfstypen

Partial · Pick · Omit · Record · ReturnType
type UserUpdate = Partial<User>; type UserContact = Pick<User, "name" | "email">; type UserWithoutId = Omit<User, "id">;

Generic Utility Types sind eingebaute Hilfstypen, die häufige Typ-Transformationen durchführen. Sie sind selbst generisch und arbeiten mit beliebigen Typen.

Die wichtigsten Utility Types

Utility Beschreibung Beispiel
Partial<T> Macht alle Eigenschaften optional { id?: number; name?: string }
Required<T> Macht alle Eigenschaften erforderlich { id: number; name: string }
Readonly<T> Macht alle Eigenschaften schreibgeschützt { readonly id: number; readonly name: string }
Pick<T, K> Wählt nur bestimmte Schlüssel aus { name: string; email: string }
Omit<T, K> Entfernt bestimmte Schlüssel { name: string; email: string }
Record<K, T> Erstellt einen Objekttyp mit Schlüsseln K { [key: string]: User }
Exclude<T, U> Entfernt Typen aus einer Union string | number
Extract<T, U> Extrahiert Typen aus einer Union string
ReturnType<T> Rückgabetyp einer Funktion number
Parameters<T> Parameter-Typen einer Funktion [string, number]
NonNullable<T> Entfernt null und undefined string | number
Beispiele
interface User {
id: number;
name: string;
email: string;
role: string;
}
// Partial: Alle Eigenschaften optional
type UserUpdate = Partial<User>;
// Pick: Nur Name und Email
type UserContact = Pick<User, "name" | "email">;
// Omit: Alle außer ID und Role
type UserPublic = Omit<User, "id" | "role">;
// Record: Dictionary von Users
type UserDictionary = Record<string, User>;
// ReturnType: Rückgabetyp einer Funktion
const getUserId = (user: User) => user.id;
type UserId = ReturnType<typeof getUserId>; // number
Tipp: Utility Types sind die Grundlage für viele TypeScript-Patterns. Sie helfen, Code zu reduzieren und Typen präzise zu definieren. Kombinieren Sie sie mit anderen Generics für maximale Flexibilität.

TypeScript Generics im Überblick

<T> Generics definieren
Funktionen, Interfaces, Klassen
extends Constraints
Typen einschränken
infer Typen extrahieren
Conditional Types
keyof Schlüssel eines Objekts
Für Constraints
Utility Hilfstypen
Partial, Pick, Omit, etc.
typeof Typ einer Variable
Für Runtime-Typen

Quick Summary

<T>
Generic Functions
interface<T>
Generic Interfaces
class<T>
Generic Classes
extends
Constraints
infer
Typen extrahieren
Partial
Utility Types
function identity<T>(value: T): T { return value; } · type UserUpdate = Partial<User>