TypeScript-Generics für den Alltag: Ein praktischer Leitfaden

Frontend2026-10-04TryQuickToolBox

Du schreibst eine Funktion, die mit jedem Typ funktioniert, aber TypeScript beschwert sich ständig über any. Oder du hast dieselbe Funktion dreimal kopiert, nur um verschiedene Typen zu verarbeiten. Generics lösen dieses Problem – sie ermöglichen es dir, Code zu schreiben, der mit mehreren Typen funktioniert und dabei die Typsicherheit bewahrt. Doch viele Entwickler vermeiden sie entweder oder übertreiben es mit ihnen. Dieser Leitfaden zeigt dir, wie du Generics im Alltag ohne Kopfschmerzen einsetzt.

Was sind Generics und warum solltest du dich dafür interessieren?

Generics sind eine Möglichkeit, Typen zu parametrisieren. Anstatt einen Typ wie string oder number fest zu codieren, verwendest du eine Typvariable (oft T), die durch einen konkreten Typ ersetzt wird, wenn die Funktion oder Klasse verwendet wird.

Warum ist das wichtig? Weil es dir ermöglicht, wiederverwendbaren Code zu schreiben, der typsicher bleibt. Ohne Generics würdest du entweder Code duplizieren oder any verwenden, was den Zweck von TypeScript untergräbt.

Generische Funktionen: Der Baustein

Beginnen wir mit einem einfachen Beispiel. Angenommen, du möchtest eine Funktion, die das erste Element eines Arrays zurückgibt.

function firstElement(arr: any[]): any {
  return arr[0];
}

Das funktioniert, aber du verlierst Typinformationen. Wenn du ein Array von Strings übergibst, ist der Rückgabetyp any, nicht string. Jetzt mit Generics:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

Jetzt weiß TypeScript, dass du, wenn du ein string[] übergibst, ein string | undefined zurückbekommst. Der Typ bleibt erhalten.

Du kannst auch mehrere Typparameter verwenden:

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

Generische Interfaces und Typ-Aliase

Generics sind nicht auf Funktionen beschränkt. Du kannst generische Interfaces und Typ-Aliase erstellen, um flexible Datenstrukturen zu beschreiben.

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

Das ist besonders nützlich für API-Antworten. Anstatt für jeden Endpunkt ein neues Interface zu definieren, kannst du ein generisches ApiResponse<T> definieren:

interface ApiResponse<T> {
  data: T;
  status: number;
}

type User = { id: number; name: string };

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

Generische Constraints: Regeln hinzufügen

Manchmal möchtest du einschränken, welche Typen mit deinem Generic verwendet werden können. Hier kommen Constraints ins Spiel. Verwende das extends-Schlüsselwort, um zu verlangen, dass der Typ bestimmte Eigenschaften hat.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Fehler: number hat kein length

Constraints machen deine Generics vorhersehbarer und verhindern Laufzeitfehler.

Generische Klassen

Klassen können ebenfalls generisch sein. Das ist praktisch für Datenstrukturen wie Stacks, Queues oder Repositories.

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined

Wann Generics verwenden (und wann nicht)

Generics sind mächtig, aber sie können den Code schwerer lesbar machen, wenn sie übermäßig verwendet werden. Hier ist eine kurze Anleitung:

Generics verwenden, wenn Generics vermeiden, wenn
Du Typbeziehungen bewahren musst Ein einfacher Union-Typ oder any ausreicht
Du wiederverwendbare Utilities schreibst Der Typ immer derselbe ist
Du Code-Duplikation vermeiden willst Es den Code schwerer verständlich macht

Eine gute Faustregel: Wenn du dieselbe Logik für mehrere Typen schreibst, ziehe Generics in Betracht. Wenn du einen dritten Typparameter hinzufügst und niemand ihn versteht, mach einen Schritt zurück.

Häufige Muster und Best Practices

Praxisbeispiel: Ein generischer API-Client

Lass uns alles mit einem praktischen Beispiel zusammenführen: ein einfacher API-Client, der Daten abruft und eine typisierte Antwort zurückgibt.

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

Dieses Muster ist im Frontend-Code üblich. Du bekommst Typsicherheit, ohne dich für jeden Endpunkt zu wiederholen.

FAQ

Was bedeutet die <T>-Syntax in TypeScript?

<T> deklariert einen Typparameter. Es ist ein Platzhalter für einen Typ, der angegeben wird, wenn die Funktion, das Interface oder die Klasse verwendet wird. Du kannst ihn beliebig benennen, aber T ist für einen einzelnen Typ üblich.

Kann ich mehrere generische Typparameter verwenden?

Ja. Du kannst so viele definieren, wie du brauchst, getrennt durch Kommas: <T, U, V>. Verwende beschreibende Namen, um deinen Code lesbar zu halten.

Wie beschränke ich ein Generic auf Objekte?

Verwende T extends object. Das stellt sicher, dass T ein Objekttyp ist und kein primitiver Typ wie string oder number. Du kannst auch auf bestimmte Strukturen einschränken, wie T extends { id: number }.

Generics sind ein Kernbestandteil beim Schreiben von wartbarem TypeScript. Wenn du mit JSON-Daten arbeitest und sie schnell validieren oder formatieren möchtest, probiere unseren JSON Formatter aus, um deine API-Antworten übersichtlich darzustellen und zu inspizieren.