Ordnung im Code-Dschungel: TypeScript Generics erklärt
Tags: typescript, javascript, generics, programming
Published: 2024-01-07 15:57:32

## Einleitung

_Typescript_ ist ein sehr beliebtes Werkzeug um Typsicherheit in _JavaScript_ zu erlangen. Eines der leistungsstärksten Features in diesem System sind [**Generics**](https://www.typescriptlang.org/docs/handbook/2/generics.html), 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:

```typescript
function sortArray(
  array: number[],
  compareFn: (a: number, b: number) => number
): number[] {
  const arrayCopy = [...array];
  arrayCopy.sort(compareFn);
  return arrayCopy;
}
```

`sortArray` erwartet ein `array` vom Typ `number` sowie eine `function` die an [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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:

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

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:

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

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:

```typescript
function sortArray<T>(array: T[], compareFn: (a: T, b: T) => number): T[] {
```

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

```typescript
const strings = ["hans", "adam", "egon"];
const sortedStrings = sortArray(strings, (a, b) => a.localeCompare(b));
```

im Prinzip folgendes:

```typescript
function sortArray<string>(array: string[], compareFn: (a: string, b: string) => number): string[] {
```

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](https://de.wikipedia.org/wiki/Programmierschnittstelle)). Nehmen wir an wir haben folgenden `type` definiert:

```typescript
type APIResponse = {
  data: {
    user: User;
  };
  error: null | Error;
  status: number;
};
```

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:

```typescript
type APIResponse<Data> = {
  data: Data;
  error: null | Error;
  status: number;
};
```

und dann beispielsweise so nutzen:

```typescript
const res: APIResponse<{ user: User }> = {
  data: {
    user: { id: 6, name: "Beatrice" },
  },
  error: null,
  status: 200,
};
```

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 **H**igher **O**rder **C**omponents. 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.