Einleitung: Was sind Generics und warum sind sie wichtig?
\n\nBeim Erlernen von TypeScript stößt man schnell auf Situationen, in denen man eine Funktion oder Klasse schreiben muss, die mit verschiedenen Datentypen arbeiten kann. Ohne spezielle Werkzeuge müsste man entweder den Code für jeden Typ duplizieren oder any verwenden, was die ganze Magie der strengen Typisierung zunichtemacht. Genau hier kommen Generics ins Spiel.
Generics sind ein leistungsstarker Mechanismus in TypeScript, der es ermöglicht, Komponenten (Funktionen, Klassen, Schnittstellen) zu erstellen, die mit verschiedenen Typen arbeiten, während die strenge Typisierung erhalten bleibt. Stellen Sie sich vor, Sie schreiben einen universellen Container, der Äpfel, Bücher und Zahlen speichern kann, aber TypeScript genau weiß, was sich in jedem einzelnen Fall darin befindet.
\n\nIn diesem Artikel werden wir untersuchen, wozu Generics dienen, wie man sie deklariert und in realen Projekten verwendet. Sie erfahren, wie Sie Ihren Code flexibler, sicherer und wiederverwendbarer machen können.
\n\nGrundlagen von Generics: Syntax und erste Funktion
\n\nBeginnen wir mit einem einfachen Beispiel. Angenommen, wir brauchen eine Funktion, die ein Argument nimmt und dasselbe zurückgibt (Identitätsfunktion). Ohne Generics würden wir so schreiben:
\n\nfunction identity(arg: number): number {\n return arg;\n}\n\nAber diese Funktion funktioniert nur mit Zahlen. Um sie universell zu machen, könnten wir any verwenden:
function identity(arg: any): any {\n return arg;\n}\n\nDas Problem ist, dass wir die Typinformationen verlieren. TypeScript weiß nicht, dass genau derselbe Typ zurückgegeben wird, der übergeben wurde. Hier kommen Generics zur Hilfe:
\n\nfunction identity<T>(arg: T): T {\n return arg;\n}\n\n// Verwendung:\nlet output1 = identity<string>("Hallo"); // Typ string\nlet output2 = identity<number>(42); // Typ number\n\nLassen Sie uns die Syntax analysieren:
\n- \n
<T>— das ist der Typparameter. Sie können jeden Buchstaben verwenden, aber üblicherweise wirdT(Type),U,Vverwendet. \n arg: T— das Argument hat den TypT. \n : Tnach den Klammern — die Funktion gibt einen Wert vom TypTzurück. \n
Jetzt weiß TypeScript: Wenn Sie einen String übergeben haben, wird auch ein String ausgegeben. Wenn eine Zahl, dann eine Zahl. Die Typinferenz funktioniert oft automatisch, daher kann man einfach identity("Hallo") schreiben — TypeScript versteht selbst, dass T ein string ist.
Generics in Funktionen: Arbeiten mit Arrays und Einschränkungen
\n\nAm häufigsten werden Generics für die Arbeit mit Arrays oder Sammlungen verwendet. Betrachten wir eine Funktion, die das erste Element eines Arrays zurückgibt:
\n\nfunction getFirst<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nconst numbers = [1, 2, 3];\nconst firstNumber = getFirst(numbers); // Typ number\n\nconst strings = ["a", "b", "c"];\nconst firstString = getFirst(strings); // Typ string\n\nBeachten Sie: T[] ist eine Kurzschreibweise für Array<T>. Die Funktion arbeitet mit jedem Array, gibt aber ein Element genau des Typs zurück, der im Array gespeichert ist.
Einschränkungen (Constraints) mit extends
\n\nManchmal muss der Typ T bestimmte Eigenschaften haben. Zum Beispiel möchten wir die Länge eines Objekts ermitteln. Nicht alle Typen haben eine length-Eigenschaft, daher können wir eine Einschränkung auferlegen:
interface HasLength {\n length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n return arg.length;\n}\n\ngetLength("Zeichenkette"); // OK, String hat length\ngetLength([1, 2, 3]); // OK, Array hat length\ngetLength(42); // Fehler! Zahl hat keine length