TypeScript Generics im Alltag
Du schreibst eine Funktion, die das erste Element eines Arrays zurückgibt. Sie soll für Zahlen, Strings und Objekte funktionieren, aber der Rückgabetyp soll zum Eingabetyp passen. Ohne Generics würdest du auf any zurückgreifen und die gesamte Typensicherheit verlieren. Hier kommen TypeScript Generics ins Spiel.
Generics ermöglichen es dir, Code zu schreiben, der mit mehreren Typen funktioniert und dabei die spezifischen Typinformationen beibehält. In diesem Leitfaden erkunden wir praktische Muster für die Verwendung von Generics im alltäglichen TypeScript-Code.
Was sind Generics?
Generics sind eine Möglichkeit, Typen zu parametrisieren. So wie Funktionen Parameter annehmen, nehmen Generics Typparameter an. Sie erlauben dir, wiederverwendbare Komponenten zu erstellen, die mit einer Vielzahl von Typen statt nur einem einzigen funktionieren.
Betrachte diese Identitätsfunktion:
function identity<T>(arg: T): T {
return arg;
}
Hier ist T eine Typvariable. Wenn du identity(42) aufrufst, leitet TypeScript T als number ab. Wenn du identity("hello") aufrufst, wird T zu string. Der Rückgabetyp entspricht immer dem Eingabetyp.
Generische Funktionen in der Praxis
Schauen wir uns ein häufiges Szenario an: eine Funktion, die einen Wert in ein Objekt verpackt.
function wrapInObject<T>(value: T): { value: T } {
return { value };
}
Ohne Generics müsstest du any oder unknown verwenden, und Aufrufer würden Typinformationen verlieren. Mit Generics ist der Rückgabetyp präzise { value: T }.
Ein weiteres alltägliches Beispiel ist eine Funktion, die das letzte Element eines Arrays zurückgibt:
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
TypeScript leitet T aus dem Array-Argument ab, sodass last([1, 2, 3]) number | undefined zurückgibt.
Generische Constraints
Manchmal möchtest du die Typen einschränken, die mit deinem Generic verwendet werden können. Zum Beispiel könntest du auf eine Eigenschaft zugreifen müssen, die nur bei bestimmten Typen existiert. Du kannst das extends-Schlüsselwort verwenden, um eine Einschränkung hinzuzufügen.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
Jetzt muss T eine length-Eigenschaft vom Typ number haben. Das funktioniert für Strings, Arrays und jedes Objekt mit einer length-Eigenschaft.
Du kannst Constraints auch mit Interfaces verwenden:
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
Dies stellt sicher, dass die an findById übergebenen Elemente eine id-Eigenschaft haben.
Generische Interfaces und Type Aliases
Generics sind nicht auf Funktionen beschränkt. Du kannst generische Interfaces und Type Aliases definieren, um flexible Datenstrukturen zu erstellen.
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
const data = await response.json();
return { data, status: response.status };
}
Jetzt kannst du API-Antworten präzise typisieren:
interface User {
id: number;
name: string;
}
const userResponse = await fetchData<User>('/api/user');
// userResponse.data is User
Generische Klassen
Klassen können ebenfalls generisch sein. Ein häufiges Beispiel ist ein einfacher Stack:
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];
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
const num = numberStack.pop(); // number | undefined
Generics in React-Komponenten
Wenn du React-Komponenten baust, können Generics deine Komponenten wiederverwendbarer machen. Zum Beispiel eine Listenkomponente, die Elemente beliebigen Typs rendert:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, index) => <li key={index}>{renderItem(item)}</li>)}</ul>;
}
Jetzt kannst du List mit jedem Typ verwenden und erhältst vollständige Typprüfung für den renderItem-Callback.
Häufige Fallstricke und Best Practices
- Übertreibe es nicht mit Generics. Wenn eine Funktion nur mit einem bestimmten Typ funktioniert, verwende einfach diesen Typ. Generics erhöhen die Komplexität.
- Verwende beschreibende Typparameternamen. Obwohl
Tüblich ist, verwende bei mehreren Parametern Namen wieTInput,TOutput. - Bevorzuge Constraints gegenüber
any. Constraints bieten dir Typsicherheit und bleiben flexibel. - Denke daran, dass Generics zur Laufzeit gelöscht werden. Sie existieren nur zur Kompilierungszeit.
Vergleich: Generics vs. any vs. Union Types
| Ansatz | Typsicherheit | Flexibilität | Anwendungsfall |
|---|---|---|---|
any |
Keine | Hoch | JS-Migration, schnelle Prototypen |
| Union Types | Gut | Auf bekannte Typen beschränkt | Wenn du alle möglichen Typen kennst |
| Generics | Stark | Hoch | Wiederverwendbare Komponenten mit erhaltenen Typen |
FAQ
Wann sollte ich Generics anstelle von any verwenden?
Verwende Generics immer dann, wenn du Typinformationen über eine Funktion oder Komponente hinweg erhalten möchtest. any deaktiviert die Typprüfung, während Generics sie beibehalten. Wenn du dich dabei ertappst, zu any zu greifen, um etwas zum Laufen zu bringen, überlege, ob ein Generic es sicherer lösen könnte.
Kann ich mehrere Typparameter verwenden?
Ja. Du kannst Funktionen mit mehreren Typparametern definieren, wie function pair<K, V>(key: K, value: V): [K, V]. Verwende beschreibende Namen, wenn es mehr als einen gibt.
Wie funktionieren Generics mit Arrow-Funktionen in .tsx-Dateien?
In .tsx-Dateien kann <T> mehrdeutig mit JSX sein. Verwende ein nachgestelltes Komma: const identity = <T,>(arg: T): T => arg; oder verwende extends unknown: <T extends unknown>.
Generics sind eine leistungsstarke Funktion, die deinen TypeScript-Code wiederverwendbarer und typsicherer machen kann. Beginne mit einfachen generischen Funktionen und wende sie dann schrittweise auf Interfaces und Klassen an. Wenn du JSON-Daten mit Typsicherheit formatieren oder validieren musst, schau dir unser JSON Formatter-Tool an, um dein JSON schnell zu inspizieren und übersichtlich zu formatieren.