Generics in TypeScript verstehen

Generics machen deinen Code wiederverwendbar, ohne die Typsicherheit aufzugeben. Du lernst, wie Typ-Platzhalter funktionieren und wann du sie einsetzt.

Teilen
Generics in TypeScript verstehen

Stell dir vor, du schreibst eine Funktion, die das erste Element eines Arrays zurückgibt. Soll sie für Zahlen, Zeichenketten und Objekte funktionieren? Mit fest verdrahteten Typen müsstest du sie mehrfach schreiben. Genau hier kommen Generics ins Spiel. Sie erlauben dir, mit Typ-Platzhaltern zu arbeiten, sodass dein Code flexibel bleibt und trotzdem typsicher ist. In diesem Beitrag schauen wir uns an, wie das funktioniert.

Das Problem ohne Generics

Ohne Generics hast du zwei unschöne Möglichkeiten: Entweder du nutzt any und verlierst jede Typsicherheit, oder du schreibst denselben Code für jeden Typ neu:

// Variante mit any: unsicher
function ersterEintragUnsicher(liste: any[]): any {
  return liste[0];
}

const wert = ersterEintragUnsicher([1, 2, 3]);
// wert hat den Typ any, der Compiler hilft nicht mehr

// Variante mit Duplikaten: mühsam
function ersterEintragZahl(liste: number[]): number {
  return liste[0];
}
function ersterEintragText(liste: string[]): string {
  return liste[0];
}

Beide Wege sind unbefriedigend. Generics lösen das Problem elegant.

Die erste generische Funktion

Mit einem Typ-Parameter, üblicherweise T genannt, machst du den Typ zur Variable. Er wird beim Aufruf automatisch bestimmt:

function ersterEintrag<T>(liste: T[]): T {
  return liste[0];
}

const zahl = ersterEintrag<number>([1, 2, 3]); // Typ: number
const text = ersterEintrag(["a", "b", "c"]);    // Typ wird als string erkannt

// Der Compiler kennt jetzt den genauen Typ:
console.log(zahl.toFixed(2)); // erlaubt, weil number
console.log(text.toUpperCase()); // erlaubt, weil string

Das <T> ist der Platzhalter. Beim Aufruf kannst du den Typ angeben oder ihn von TypeScript erkennen lassen.

Generics mit mehreren Typ-Parametern

Du bist nicht auf einen Platzhalter beschränkt. Eine Funktion, die zwei Werte zu einem Paar verbindet, braucht zwei Typen:

function paar<A, B>(erstes: A, zweites: B): [A, B] {
  return [erstes, zweites];
}

const p1 = paar("Alter", 30);        // [string, number]
const p2 = paar(true, [1, 2, 3]);    // [boolean, number[]]

console.log(p1[0]); // "Alter"
console.log(p1[1]); // 30

Generics einschränken mit extends

Manchmal soll ein generischer Typ nicht beliebig sein, sondern bestimmte Eigenschaften besitzen. Mit extends legst du eine Bedingung fest:

interface MitLaenge {
  length: number;
}

function zeigeLaenge<T extends MitLaenge>(eingabe: T): number {
  return eingabe.length;
}

console.log(zeigeLaenge("Hallo"));      // 5, string hat length
console.log(zeigeLaenge([1, 2, 3, 4])); // 4, array hat length
// console.log(zeigeLaenge(42)); // Fehler: number hat kein length

So stellst du sicher, dass die Funktion nur mit Werten arbeitet, die eine length-Eigenschaft besitzen.

Generische Interfaces und Klassen

Generics funktionieren nicht nur in Funktionen. Auch Interfaces und Klassen können Typ-Parameter aufnehmen. Das ist besonders nützlich für wiederverwendbare Container:

interface Antwort<T> {
  erfolg: boolean;
  daten: T;
}

const benutzerAntwort: Antwort<string> = {
  erfolg: true,
  daten: "Anna"
};

// Eine kleine generische Klasse als Stapel
class Stapel<T> {
  private elemente: T[] = [];

  hinzufuegen(element: T): void {
    this.elemente.push(element);
  }

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

const zahlenStapel = new Stapel<number>();
zahlenStapel.hinzufuegen(1);
zahlenStapel.hinzufuegen(2);
console.log(zahlenStapel.entnehmen()); // 2

Fazit

Generics sind ein mächtiges Werkzeug, um Code wiederverwendbar zu gestalten, ohne die Typsicherheit zu opfern. Du beginnst mit einem einfachen Typ-Parameter T, erweiterst bei Bedarf auf mehrere und schränkst sie mit extends ein, wenn bestimmte Eigenschaften nötig sind. Auch Interfaces und Klassen profitieren davon. Am Anfang wirken die spitzen Klammern vielleicht ungewohnt, doch sobald du den ersten generischen Container selbst gebaut hast, möchtest du sie nicht mehr missen. Probiere es in deinem nächsten Projekt aus.