Einleitung: Warum verliert TypeScript ohne Generics die Hälfte seiner Stärke?
Stellen Sie sich vor, Sie sind ein Architekt, der jedes Mal ein Gebäude von Grund auf neu entwirft, auch wenn Sie nur eine weitere Garage bauen müssen. So ungefähr fühlt sich ein Entwickler, der dieselbe Funktion für Zahlen, Zeichenketten und Arrays schreibt, ohne TypeScript Generics zu verwenden. Kommt Ihnen das bekannt vor? Wenn Sie bereits die grundlegenden Typen in TypeScript beherrschen, aber das Gefühl haben, zu viel doppelten Code zu schreiben, ist dieser Artikel genau das Richtige für Sie.
Generics sind ein Mechanismus, der es ermöglicht, Komponenten (Funktionen, Klassen, Schnittstellen) zu erstellen, die mit verschiedenen Typen arbeiten, anstatt mit einem einzigen spezifischen. Anstatt Code zu kopieren, schreiben Sie ihn einmal, und der Typ wird automatisch eingesetzt. In diesem Artikel werden wir uns ansehen, wie generische Programmierung in TypeScript funktioniert, zeigen reale Codebeispiele und geben praktische Tipps, die Ihren Code sauberer und sicherer machen.
Wir sind PythonLib, und heute sprechen wir über TypeScript, weil moderne Frontend-Entwicklung ohne ihn undenkbar ist. Los geht's!
Was sind Generics in TypeScript und warum werden sie benötigt?
Beginnen wir mit einem einfachen Beispiel. Angenommen, Sie möchten eine Funktion schreiben, die das ihr übergebene Argument zurückgibt (Identitätsfunktion).
// Ohne Generics – man muss any verwendenfunction identity(arg: any): any { return arg;}
// Oder Überladungen für jeden Typ schreibenfunction identityNumber(arg: number): number { return arg; }function identityString(arg: string): string { return arg; }Die erste Variante zerstört die Typisierung – Sie verlieren die Typinformation. Die zweite zwingt Sie, eine Vielzahl identischer Funktionen zu erstellen. TypeScript Generics lösen dieses Problem elegant:
function identity<T>(arg: T): T { return arg;}
const num = identity<number>(42); // Typ numberconst str = identity('hallo'); // Typ string (Typinferenz)const bool = identity(true); // Typ booleanDas Symbol <T> ist ein Typparameter. Sie können ihn beliebig nennen (häufig werden T, U, V verwendet), aber es ist besser, aussagekräftige Namen zu vergeben, wenn es mehrere gibt. Wenn Sie die Funktion aufrufen, setzt TypeScript den tatsächlichen Typ anstelle von T ein. Wenn der Typ nicht explizit angegeben wird, leitet der Compiler ihn automatisch aus dem Argument ab.
Auf diese Weise ermöglichen generische Typen (Generics) die Beibehaltung einer strengen Typisierung dort, wo früher any verwendet werden musste. Dies reduziert die Anzahl der Fehler und macht den Code selbstdokumentierend.
Syntax der Generics: Vom Einfachen zum Komplexen
Generic Funktionen
Wir haben bereits ein grundlegendes Beispiel gesehen. Machen wir es komplexer: eine Funktion, die ein Array nimmt und sein erstes Element zurückgibt.
function getFirst<T>(arr: T[]): T | undefined { return arr[0];}
const firstNumber = getFirst([1, 2, 3]); // number | undefinedconst firstString = getFirst(['a', 'b', 'c']); // string | undefinedBeachten Sie: T[] ist eine Kurzform von Array<T>. Sie können jede Form verwenden.
Generic Interfaces
Schnittstellen mit Generics sind ein mächtiges Werkzeug zur Beschreibung von Datenstrukturen. Zum Beispiel eine typisierte API-Antwort:
interface ApiResponse<T> { data: T; status: number; message: string;}
// Verwendungconst userResponse: ApiResponse<{ id: number; name: string }> = { data: { id: 1, name: 'Alice' }, status: 200, message: 'OK'};Jetzt kann eine einzige Schnittstelle Antworten für Benutzer, Produkte, Bestellungen beschreiben – es genügt, den entsprechenden Typ einzusetzen.
Generic Klassen
Klassen können ebenfalls generisch sein. Ein klassisches Beispiel ist eine Warteschlange (Queue):
c