TypeScript Generics - Wiederverwendbare Typen
Generics erlauben flexible, wiederverwendbare Komponenten die mit verschiedenen Typen arbeiten.
Was sind Generics?
Ohne Generics müsstest du Code duplizieren:
// Für numbers
function identityNumber(value: number): number {
return value
}
// Für strings
function identityString(value: string): string {
return value
}
// Für booleans
function identityBoolean(value: boolean): boolean {
return value
}
Mit Generics brauchst du nur eine Funktion:
function identity<T>(value: T): T {
return value
}
const num = identity(42) // Type: number
const str = identity("Hello") // Type: string
const bool = identity(true) // Type: boolean
<T> ist ein Type Parameter - ein Platzhalter für einen Typ!
Generic Functions
Basic Syntax
function firstElement<T>(arr: T[]): T | undefined {
return arr[0]
}
const num = firstElement([1, 2, 3]) // Type: number | undefined
const str = firstElement(["a", "b"]) // Type: string | undefined
Mehrere Type Parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second]
}
const p1 = pair("Max", 25) // Type: [string, number]
const p2 = pair(true, "Active") // Type: [boolean, string]
Generic Constraints
Type Parameters einschränken:
// T muss length Property haben
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length)
}
logLength("Hello") // ✅ OK: string has length
logLength([1, 2, 3]) // ✅ OK: array has length
logLength(42) // ❌ Error: number has no length
Häufige Constraints:
// Muss Object sein (nicht primitive)
function clone<T extends object>(obj: T): T {
return { ...obj }
}
// Muss Array sein
function first<T extends any[]>(arr: T): T[0] {
return arr[0]
}
// Muss Interface implementieren
interface Named {
name: string
}
function greet<T extends Named>(obj: T): string {
return `Hello ${obj.name}`
}
Generic Interfaces
interface Box<T> {
value: T
getValue(): T
setValue(value: T): void
}
const numberBox: Box<number> = {
value: 42,
getValue() { return this.value },
setValue(value) { this.value = value }
}
const stringBox: Box<string> = {
value: "Hello",
getValue() { return this.value },
setValue(value) { this.value = value }
}
Generic Interface mit mehreren Types
interface Pair<K, V> {
key: K
value: V
}
const pair1: Pair<string, number> = {
key: "age",
value: 25
}
const pair2: Pair<number, string> = {
key: 1,
value: "First"
}
Generic Classes
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
peek(): T | undefined {
return this.items[this.items.length - 1]
}
isEmpty(): boolean {
return this.items.length === 0
}
}
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
console.log(numberStack.pop()) // 2
const stringStack = new Stack<string>()
stringStack.push("Hello")
stringStack.push("World")
Generic Class mit Constraints
interface Comparable<T> {
compareTo(other: T): number
}
class SortedList<T extends Comparable<T>> {
private items: T[] = []
add(item: T): void {
this.items.push(item)
this.items.sort((a, b) => a.compareTo(b))
}
get(index: number): T | undefined {
return this.items[index]
}
}
keyof Operator
Für Type-Safe Property Access:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const person = {
name: "Max",
age: 25,
email: "max@test.com"
}
const name = getProperty(person, "name") // Type: string
const age = getProperty(person, "age") // Type: number
const invalid = getProperty(person, "address") // ❌ Error
Real-World Beispiel:
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach(key => {
result[key] = obj[key]
})
return result
}
const user = {
id: 1,
name: "Max",
email: "max@test.com",
password: "secret"
}
const publicUser = pick(user, ["id", "name", "email"])
// Type: { id: number; name: string; email: string }
Generic Type Aliases
type Result<T> = { success: true; data: T } | { success: false; error: string }
function fetchUser(id: number): Result<User> {
// ...
}
const result = fetchUser(1)
if (result.success) {
console.log(result.data.name) // Type: User
} else {
console.log(result.error) // Type: string
}
Promise<T>
async function fetchData(): Promise<string> {
return "Data"
}
async function fetchUser(): Promise<{ name: string; age: number }> {
return { name: "Max", age: 25 }
}
// TypeScript weiß: data ist string
const data = await fetchData()
// TypeScript weiß: user ist { name: string; age: number }
const user = await fetchUser()
Default Type Parameters
interface Response<T = string> {
data: T
status: number
}
// Mit Default: T = string
const response1: Response = {
data: "Success",
status: 200
}
// Mit explizitem Type
const response2: Response<{ id: number }> = {
data: { id: 1 },
status: 200
}
Generic Constraints mit typeof
function createInstance<T>(constructor: new () => T): T {
return new constructor()
}
class Person {
name: string = "Unknown"
}
const person = createInstance(Person)
console.log(person.name) // "Unknown"
interface ApiResponse<T> {
data: T
status: number
message?: string
}
class ApiClient {
async get<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url)
return {
data: await response.json(),
status: response.status
}
}
async post<T, U>(url: string, body: U): Promise<ApiResponse<T>> {
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(body)
})
return {
data: await response.json(),
status: response.status
}
}
}
// Usage
interface User {
id: number
name: string
}
const client = new ApiClient()
const response = await client.get<User[]>("/api/users")
// response.data ist Type: User[]
const newUser = await client.post<User, { name: string }>("/api/users", {
name: "Max"
})
// newUser.data ist Type: User📝 Quiz
Was macht der keyof Operator?
Tipps & Tricks
Generic Type Inference
TypeScript kann Generics oft automatisch erkennen:
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn)
}
// TypeScript erkennt automatisch:
// T = number, U = string
const result = map([1, 2, 3], n => n.toString())
Conditional Types mit Generics
type IsArray<T> = T extends any[] ? true : false
type A = IsArray<number[]> // true
type B = IsArray<string> // false
// Praktisches Beispiel
type Flatten<T> = T extends any[] ? T[number] : T
type Num = Flatten<number[]> // number
type Str = Flatten<string> // string
Mapped Types mit Generics
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
interface User {
name: string
age: number
}
type NullableUser = Nullable<User>
// { name: string | null; age: number | null }
Häufige Fehler
Fehler 1: Zu generische Generics
❌ FALSCH:
function process<T>(data: T): T {
return data.map(x => x * 2) // ❌ Error: T hat kein map
}
✅ RICHTIG:
function process<T extends number[]>(data: T): T {
return data.map(x => x * 2) as T
}
Fehler 2: any statt Generics
❌ FALSCH:
function identity(value: any): any {
return value
}
const result = identity(42) // Type: any (nicht number!)
✅ RICHTIG:
function identity<T>(value: T): T {
return value
}
const result = identity(42) // Type: number
Fehler 3: Generics bei Simple Cases
❌ ÜBERTRIEBEN:
function add<T extends number>(a: T, b: T): number {
return a + b
}
✅ BESSER:
function add(a: number, b: number): number {
return a + b
}
Regel: Nutze Generics nur wenn du sie brauchst!
Zusammenfassung
Du hast gelernt:
- ✅ Generics für wiederverwendbare Komponenten
- ✅ Type Parameters mit
<T> - ✅ Generic Constraints mit
extends - ✅ Generic Functions, Interfaces, Classes
- ✅ keyof für Type-Safe Property Access
- ✅ Default Type Parameters
- ✅ Promise<T> und async/await
Key Takeaways:
- Generics = Platzhalter für Typen
- Constraints mit
extends - keyof für Property-Namen
- Type Inference nutzen wo möglich
- Nicht übertreiben - nur bei Bedarf nutzen
Nächste Schritte
Als Nächstes lernst du:
- Union und Intersection Types
- Type Guards und Narrowing
- Utility Types (Pick, Omit, Partial, etc.)
- Advanced Types
Viel Erfolg! 🚀
Gut gemacht! 🎉
Du hast "TypeScript Generics - Wiederverwendbare Typen" abgeschlossen
Artikel bewerten
Bitte einloggen um zu bewerten
Das könnte dich auch interessieren
TypeScript Basic Types - Primitive Datentypen
Lerne die grundlegenden Datentypen in TypeScript kennen: string, number, boolean, arrays, tuples und mehr.
TypeScript Union & Intersection Types
Lerne Union Types (|) und Intersection Types (&) für flexible und präzise Typdefinitionen.
TypeScript Utility Types - Eingebaute Type Helpers
Lerne die wichtigsten eingebauten Utility Types: Partial, Required, Pick, Omit, Record und mehr.