TypeScript-Generics für den Alltag: Ein praktischer Leitfaden
Du schreibst eine Funktion, die mit jedem Typ funktioniert, aber TypeScript beschwert sich ständig über any. Oder du hast dieselbe Funktion dreimal kopiert, nur um verschiedene Typen zu verarbeiten. Generics lösen dieses Problem – sie ermöglichen es dir, Code zu schreiben, der mit mehreren Typen funktioniert und dabei die Typsicherheit bewahrt. Doch viele Entwickler vermeiden sie entweder oder übertreiben es mit ihnen. Dieser Leitfaden zeigt dir, wie du Generics im Alltag ohne Kopfschmerzen einsetzt.
Was sind Generics und warum solltest du dich dafür interessieren?
Generics sind eine Möglichkeit, Typen zu parametrisieren. Anstatt einen Typ wie string oder number fest zu codieren, verwendest du eine Typvariable (oft T), die durch einen konkreten Typ ersetzt wird, wenn die Funktion oder Klasse verwendet wird.
Warum ist das wichtig? Weil es dir ermöglicht, wiederverwendbaren Code zu schreiben, der typsicher bleibt. Ohne Generics würdest du entweder Code duplizieren oder any verwenden, was den Zweck von TypeScript untergräbt.
Generische Funktionen: Der Baustein
Beginnen wir mit einem einfachen Beispiel. Angenommen, du möchtest eine Funktion, die das erste Element eines Arrays zurückgibt.
function firstElement(arr: any[]): any {
return arr[0];
}
Das funktioniert, aber du verlierst Typinformationen. Wenn du ein Array von Strings übergibst, ist der Rückgabetyp any, nicht string. Jetzt mit Generics:
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
Jetzt weiß TypeScript, dass du, wenn du ein string[] übergibst, ein string | undefined zurückbekommst. Der Typ bleibt erhalten.
Du kannst auch mehrere Typparameter verwenden:
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
Generische Interfaces und Typ-Aliase
Generics sind nicht auf Funktionen beschränkt. Du kannst generische Interfaces und Typ-Aliase erstellen, um flexible Datenstrukturen zu beschreiben.
interface Box<T> {
value: T;
}
const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };
Das ist besonders nützlich für API-Antworten. Anstatt für jeden Endpunkt ein neues Interface zu definieren, kannst du ein generisches ApiResponse<T> definieren:
interface ApiResponse<T> {
data: T;
status: number;
}
type User = { id: number; name: string };
const response: ApiResponse<User> = {
data: { id: 1, name: "Alice" },
status: 200,
};
Generische Constraints: Regeln hinzufügen
Manchmal möchtest du einschränken, welche Typen mit deinem Generic verwendet werden können. Hier kommen Constraints ins Spiel. Verwende das extends-Schlüsselwort, um zu verlangen, dass der Typ bestimmte Eigenschaften hat.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Fehler: number hat kein length
Constraints machen deine Generics vorhersehbarer und verhindern Laufzeitfehler.
Generische Klassen
Klassen können ebenfalls generisch sein. Das ist praktisch für Datenstrukturen wie Stacks, Queues oder Repositories.
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>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined
Wann Generics verwenden (und wann nicht)
Generics sind mächtig, aber sie können den Code schwerer lesbar machen, wenn sie übermäßig verwendet werden. Hier ist eine kurze Anleitung:
| Generics verwenden, wenn | Generics vermeiden, wenn |
|---|---|
| Du Typbeziehungen bewahren musst | Ein einfacher Union-Typ oder any ausreicht |
| Du wiederverwendbare Utilities schreibst | Der Typ immer derselbe ist |
| Du Code-Duplikation vermeiden willst | Es den Code schwerer verständlich macht |
Eine gute Faustregel: Wenn du dieselbe Logik für mehrere Typen schreibst, ziehe Generics in Betracht. Wenn du einen dritten Typparameter hinzufügst und niemand ihn versteht, mach einen Schritt zurück.
Häufige Muster und Best Practices
- Verwende beschreibende Namen für Typparameter.
Tist für einfache Fälle in Ordnung, aberTDataoderTResponsekönnen klarer sein. - Bevorzuge Constraints gegenüber
any. Wenn du Flexibilität brauchst, verwendeextends, um eine minimale Struktur zu erzwingen. - Nutze Standard-Typparameter. Du kannst einen Standard angeben:
<T = string>. - Verwende Generics mit Utility-Typen. TypeScripts eingebaute
Partial<T>,Readonly<T>undRecord<K, V>sind alle generisch.
Praxisbeispiel: Ein generischer API-Client
Lass uns alles mit einem praktischen Beispiel zusammenführen: ein einfacher API-Client, der Daten abruft und eine typisierte Antwort zurückgibt.
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json() as Promise<T>;
}
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
async function main() {
const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
console.log(post.title); // string
}
Dieses Muster ist im Frontend-Code üblich. Du bekommst Typsicherheit, ohne dich für jeden Endpunkt zu wiederholen.
FAQ
Was bedeutet die <T>-Syntax in TypeScript?
<T> deklariert einen Typparameter. Es ist ein Platzhalter für einen Typ, der angegeben wird, wenn die Funktion, das Interface oder die Klasse verwendet wird. Du kannst ihn beliebig benennen, aber T ist für einen einzelnen Typ üblich.
Kann ich mehrere generische Typparameter verwenden?
Ja. Du kannst so viele definieren, wie du brauchst, getrennt durch Kommas: <T, U, V>. Verwende beschreibende Namen, um deinen Code lesbar zu halten.
Wie beschränke ich ein Generic auf Objekte?
Verwende T extends object. Das stellt sicher, dass T ein Objekttyp ist und kein primitiver Typ wie string oder number. Du kannst auch auf bestimmte Strukturen einschränken, wie T extends { id: number }.
Generics sind ein Kernbestandteil beim Schreiben von wartbarem TypeScript. Wenn du mit JSON-Daten arbeitest und sie schnell validieren oder formatieren möchtest, probiere unseren JSON Formatter aus, um deine API-Antworten übersichtlich darzustellen und zu inspizieren.