Zum Inhalt springen
TypeScript Fortgeschritten 25 min

Function Overloads

Lerne wie du mit Function Overloads verschiedene Aufrufvarianten einer Funktion typisierst.

Aktualisiert:

Function Overloads

Function Overloads ermöglichen es dir, verschiedene Aufrufvarianten einer Funktion mit unterschiedlichen Typen zu definieren. Dies ist nützlich, wenn eine Funktion je nach Input unterschiedliche Output-Typen hat.

Das Problem ohne Overloads

Ohne Overloads verlierst du Typinformationen:

// Der Rückgabetyp ist immer string | number
function process(input: string | number): string | number {
    if (typeof input === "string") {
        return input.toUpperCase();
    }
    return input * 2;
}

const result1 = process("hello");  // Typ: string | number (nicht ideal!)
const result2 = process(5);        // Typ: string | number (nicht ideal!)

Function Overloads Syntax

Mit Overloads definierst du mehrere Signaturen:

// Overload Signaturen
function process(input: string): string;
function process(input: number): number;

// Implementation (muss alle Overloads abdecken)
function process(input: string | number): string | number {
    if (typeof input === "string") {
        return input.toUpperCase();
    }
    return input * 2;
}

// Jetzt sind die Typen präzise!
const result1 = process("hello");  // Typ: string
const result2 = process(5);        // Typ: number

Struktur von Overloads

// 1. Overload-Signaturen (ohne Body)
function example(a: string): string;
function example(a: number): number;
function example(a: boolean): boolean;

// 2. Implementation-Signatur (mit Body)
function example(a: string | number | boolean): string | number | boolean {
    return a;
}

Wichtig:

  • Overload-Signaturen haben keinen Funktionskörper
  • Die Implementation muss alle Overloads unterstützen
  • Die Implementation-Signatur ist nicht direkt aufrufbar

Praktische Beispiele

createElement

function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: "span"): HTMLSpanElement;
function createElement(tag: "input"): HTMLInputElement;
function createElement(tag: string): HTMLElement;

function createElement(tag: string): HTMLElement {
    return document.createElement(tag);
}

const div = createElement("div");     // HTMLDivElement
const span = createElement("span");   // HTMLSpanElement
const custom = createElement("custom"); // HTMLElement

Unterschiedliche Parameter-Anzahl

function format(value: number): string;
function format(value: number, decimals: number): string;
function format(value: number, decimals: number, locale: string): string;

function format(
    value: number,
    decimals: number = 2,
    locale: string = "de-DE"
): string {
    return value.toLocaleString(locale, {
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals
    });
}

format(1234.5);           // "1.234,50"
format(1234.5, 0);        // "1.235"
format(1234.5, 2, "en-US"); // "1,234.50"

Unterschiedliche Input/Output Kombinationen

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

// Ein User oder viele
function getUser(id: number): User;
function getUser(ids: number[]): User[];

function getUser(idOrIds: number | number[]): User | User[] {
    if (Array.isArray(idOrIds)) {
        return idOrIds.map(id => ({ id, name: `User ${id}` }));
    }
    return { id: idOrIds, name: `User ${idOrIds}` };
}

const single = getUser(1);       // User
const multiple = getUser([1, 2]); // User[]

Options-Objekt vs. einfache Parameter

type FetchOptions = {
    url: string;
    method?: string;
    body?: unknown;
};

// Einfacher Aufruf mit URL
function request(url: string): Promise<Response>;
// Aufruf mit Options
function request(options: FetchOptions): Promise<Response>;

function request(urlOrOptions: string | FetchOptions): Promise<Response> {
    const options: FetchOptions =
        typeof urlOrOptions === "string"
            ? { url: urlOrOptions }
            : urlOrOptions;

    return fetch(options.url, {
        method: options.method || "GET",
        body: options.body ? JSON.stringify(options.body) : undefined
    });
}

// Beide Aufrufe sind typsicher
request("https://api.example.com/data");
request({ url: "https://api.example.com/data", method: "POST", body: { x: 1 } });

Overloads mit Generics

function first<T>(arr: T[]): T;
function first<T>(arr: T[], count: number): T[];

function first<T>(arr: T[], count?: number): T | T[] {
    if (count !== undefined) {
        return arr.slice(0, count);
    }
    return arr[0];
}

const numbers = [1, 2, 3, 4, 5];
const one = first(numbers);        // number
const three = first(numbers, 3);   // number[]

Method Overloads in Klassen

class Calculator {
    add(a: number, b: number): number;
    add(a: string, b: string): string;
    add(a: number | string, b: number | string): number | string {
        if (typeof a === "number" && typeof b === "number") {
            return a + b;
        }
        return String(a) + String(b);
    }
}

const calc = new Calculator();
const sum = calc.add(1, 2);           // number
const concat = calc.add("a", "b");    // string

Overloads in Interfaces

interface StringParser {
    parse(input: string): string;
    parse(input: string, asJson: true): object;
}

const parser: StringParser = {
    parse(input: string, asJson?: boolean): string | object {
        if (asJson) {
            return JSON.parse(input);
        }
        return input.trim();
    }
};

Wann Overloads verwenden?

Verwende Overloads wenn:

  • Der Rückgabetyp von den Input-Typen abhängt
  • Die Anzahl der Parameter variiert
  • Du präzisere Typen für Aufrufer brauchst

Vermeide Overloads wenn:

  • Union Types ausreichen
  • Generics das Problem lösen können
  • Die Implementierung zu komplex wird

Alternative: Conditional Types

Manchmal sind Conditional Types eleganter:

// Statt Overloads...
function process(input: string): string;
function process(input: number): number;

// ...kann ein Conditional Type reichen
function process<T extends string | number>(
    input: T
): T extends string ? string : number {
    if (typeof input === "string") {
        return input.toUpperCase() as any;
    }
    return (input * 2) as any;
}

Best Practices

  1. Spezifischere Overloads zuerst

    // Richtig: spezifisch zuerst
    function fn(x: "special"): string;
    function fn(x: string): number;
    
    // Falsch: generisch zuerst (spezifischer nie erreicht)
    function fn(x: string): number;
    function fn(x: "special"): string;  // Wird nie gematcht
  2. Mindestens zwei Overloads

    • Ein einzelner Overload macht keinen Sinn
    • Nutze stattdessen Union Types
  3. Implementation muss alle Cases abdecken

    function process(a: string): string;
    function process(a: number): number;
    // Implementation muss string | number akzeptieren
    function process(a: string | number): string | number {
        // ...
    }

Zusammenfassung

  • Overloads = mehrere Funktionssignaturen
  • Präzisere Typen für verschiedene Aufrufvarianten
  • Implementation deckt alle Signaturen ab
  • Reihenfolge wichtig: spezifisch vor generell

Function Overloads sind ein mächtiges Feature für APIs, bei denen der Rückgabetyp vom Input abhängt.

Im nächsten Kapitel lernst du die Grundlagen von Generics!

Zurück zum TypeScript Kurs