Einführung in Generics in TypeScript
TypeScript ist eine streng typisierte Sprache, die JavaScript um statische Typisierung erweitert. Eine seiner leistungsstärksten und flexibelsten Funktionen ist jedoch der Mechanismus der Generics. Wenn Sie jemals Code geschrieben haben, der mit verschiedenen Datentypen arbeiten soll, dabei aber die Typsicherheit bewahren muss, dann sind Generics genau das Richtige für Sie.
Stellen Sie sich vor, Sie erstellen eine universelle Funktion, die Arrays von Zahlen, Zeichenketten oder Objekten verarbeiten kann. Ohne Generics müssten Sie entweder den Typ any verwenden (was den Nutzen von TypeScript zunichtemacht) oder separate Funktionen für jeden Typ schreiben. Generics lösen dieses Problem elegant und sicher. In diesem Artikel werden wir untersuchen, was Generics sind, warum sie benötigt werden und wie man sie effektiv in realen Projekten einsetzt.
Was sind Generics und warum werden sie benötigt?
Generics sind ein Mechanismus, der es ermöglicht, Komponenten (Funktionen, Klassen, Interfaces) zu erstellen, die mit verschiedenen Datentypen arbeiten können, ohne dabei die Typinformationen zu verlieren. Anstatt einen konkreten Typ festzulegen, verwenden Sie einen Typparameter, der zum Zeitpunkt des Aufrufs eingesetzt wird.
Das Problem ohne Generics
Angenommen, Sie möchten eine Funktion schreiben, die das erste Element eines Arrays zurückgibt. Ohne Generics könnte das so aussehen:
function getFirstElement(arr: any[]): any { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Typ 'any' — wir haben die Information verlorenconsole.log(num.toUpperCase()); // Fehler zur Laufzeit, aber TypeScript schweigtHier verwenden wir any, und TypeScript kann uns nicht warnen, dass num eine Zahl und keine Zeichenkette ist. Dies führt zu potenziellen Fehlern zur Laufzeit.
Die Lösung mit Generics
Schreiben wir nun dieselbe Funktion mit Generics um:
function getFirstElement<T>(arr: T[]): T { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Typ 'number'const str = getFirstElement(['a', 'b', 'c']); // Typ 'string'
// num.toUpperCase(); // Kompilierungsfehler — TypeScript weiß, dass num eine Zahl istDas Symbol <T> ist der Typparameter. Er fungiert als Platzhalter: Wenn wir die Funktion mit einem Zahlenarray aufrufen, wird T zu number, und der Rückgabewert erhält ebenfalls den Typ number. Auf diese Weise bewahren wir die strenge Typisierung, ohne an Flexibilität zu verlieren.
Wie man Generics verwendet: grundlegende Szenarien
Generics werden nicht nur in Funktionen, sondern auch in Klassen, Interfaces und Typen eingesetzt. Betrachten wir die häufigsten Szenarien.
Generics in Funktionen
Der einfachste und häufigste Fall sind Funktionen, die mit Daten verschiedener Typen arbeiten. Zum Beispiel eine Funktion zum Erstellen eines Schlüssel-Wert-Paares:
function createPair<K, V>(key: K, value: V): { key: K; value: V } { return { key, value };}
const pair = createPair('id', 123); // { key: string; value: number }console.log(pair.key.toUpperCase()); // Funktioniert// pair.key.toFixed(); // Fehler — key ist ein stringHier verwenden wir zwei Typparameter: K für den Schlüssel und V für den Wert. TypeScript leitet die Typen automatisch aus den Argumenten ab.
Generics in Klassen
Generics eignen sich hervorragend für die Erstellung universeller Klassen wie Stapel (Stacks), Warteschlangen (Queues) oder Datenspeicher:
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(10);numberStack.push(20);console.log(numberStack
```