DEV Community

Cover image for Ordnung im Code-Dschungel: TypeScript Generics erklärt
Pascal C
Pascal C

Posted on

Ordnung im Code-Dschungel: TypeScript Generics erklärt

Einleitung

Typescript ist ein sehr beliebtes Werkzeug um Typsicherheit in JavaScript zu erlangen. Eines der leistungsstärksten Features in diesem System sind Generics, die eine Art von Variablen für Typen darstellen. In diesem Artikel schauen wir uns an, wie Generics in Typescript funktionieren, warum sie nützlich sind und wie sie eine Codebasis flexibler und wiederverwendbarer machen können.

Definition

Generics ermöglichen, wiederverwendbare Code-Komponenten zu schreiben, die mit verschiedenen Typen arbeiten können, ohne den spezifischen Typ im Code zu verlieren. Sie funktionieren ähnlich wie die Generics in anderen Sprachen (Java oder C#). In Typescript erlauben Generics es, Typen als Parameter zu übergeben, wenn man Klassen, Schnittstellen, Funktionen und Typen definiert.

Anwendungsfälle

Basics

Nehmen wir an, wir haben folgende Funktion:

function sortArray(
  array: number[],
  compareFn: (a: number, b: number) => number
): number[] {
  const arrayCopy = [...array];
  arrayCopy.sort(compareFn);
  return arrayCopy;
}
Enter fullscreen mode Exit fullscreen mode

sortArray erwartet ein array vom Typ number sowie eine function die an sort zum sortieren übergeben wird. Jetzt ist diese Funktion nicht sonderlich hilfreich, da man nur Zahlen übergeben kann. Was wäre wenn man auch strings sortieren möchte? Die naive Vorgehensweise wäre Folgende:

function sortArray(
  array: number[] | string[],
  compareFn: (a: number | string, b: number | string) => number
): number[] | string[] {
  const arrayCopy = [...array];
  arrayCopy.sort(compareFn);
  return arrayCopy;
}
Enter fullscreen mode Exit fullscreen mode

Das funktioniert, ist aber nicht sonderlich elegant und fängt schon jetzt an die Lesbarkeit zu erschweren. Was aber wenn wir noch einen dritten, vierten, ... Typ hinzufügen? Eine Möglichkeit wäre den Typ einfach auf any zu ändern, aber dann könnte man ihn auch einfach gleich weglassen, das ist mehr Workaround als Lösung. Hier kommen Generics ins Spiel:

function sortArray<T>(array: T[], compareFn: (a: T, b: T) => number): T[] {
  const arrayCopy = [...array];
  arrayCopy.sort(compareFn);
  return arrayCopy;
}
Enter fullscreen mode Exit fullscreen mode

Was genau passiert hier?

  • sortArray<T>: Das T in den spitzen Klammern nach dem Funktionsnamen ist eine Konvention und steht für Type (Es spricht nichts dagegen expressivere Namen zu wählen und ist bei komplizierteren Typen auch angeraten).
  • Dieser Typ T ist ein Platzhalter der nun innerhalb der Funktion für die tatsächlichen Typen verwendet wird. T kann beim Aufruf der Funktion durch jeden Typ ersetzt werden.
  • So wird beispielsweise aus:
function sortArray<T>(array: T[], compareFn: (a: T, b: T) => number): T[] {
Enter fullscreen mode Exit fullscreen mode

wenn man die Funktion beispielsweise mit einem Array von strings aufruft:

const strings = ["hans", "adam", "egon"];
const sortedStrings = sortArray(strings, (a, b) => a.localeCompare(b));
Enter fullscreen mode Exit fullscreen mode

im Prinzip folgendes:

function sortArray<string>(array: string[], compareFn: (a: string, b: string) => number): string[] {
Enter fullscreen mode Exit fullscreen mode

Und das ist schon die gesamte Magie dahinter. Man kann sortArray jetzt mit jedem Typ aufrufen, durch das Generic wird der Korrekte nun automatisch inferiert.

Weitere Anwendungsfälle

Man kann Generics nicht nur für Funktionen, sondern beispielsweise auch für Typen verwenden. Beliebt ist dies oft bei der Definition von Schnittstellen (API). Nehmen wir an wir haben folgenden type definiert:

type APIResponse = {
  data: {
    user: User;
  };
  error: null | Error;
  status: number;
};
Enter fullscreen mode Exit fullscreen mode

Diese Definition müssten wir nun für jeden möglichen Antworttyp wiederholen. Generics vereinfachen dies. Wir können den Code einfach wie folgt abändern:

type APIResponse<Data> = {
  data: Data;
  error: null | Error;
  status: number;
};
Enter fullscreen mode Exit fullscreen mode

und dann beispielsweise so nutzen:

const res: APIResponse<{ user: User }> = {
  data: {
    user: { id: 6, name: "Beatrice" },
  },
  error: null,
  status: 200,
};
Enter fullscreen mode Exit fullscreen mode

Um dies noch weiter zu vereinfachen, bietet es sich an verschiedene Responsetypen zu definieren, was für dieses Beispiel aber unerheblich ist. Weitere Anwendungsfälle wären z.B. die Definition von Datenstrukturen oder bei der Verwendung von Higher Order Components. Die Möglichkeiten sind vielfältig und die Grenze mehr oder weniger die eigene Fantasie.

Schluss

Generics in TypeScript sind ein mächtiges Werkzeug, das Entwicklern die nötige Flexibilität bietet, um wiederverwendbare und typsichere Komponenten zu erstellen. Durch die Möglichkeit, Typen als Variablen zu behandeln, eröffnen sie ein neues Spektrum an Möglichkeiten, die weit über das hinausgehen, was in reinem JavaScript verfügbar ist. Sie tragen dazu bei, die Komplexität des Codes zu reduzieren, die Wiederverwendbarkeit zu erhöhen und Fehler zu minimieren, die durch inkonsistente Typisierung entstehen können.

Top comments (0)