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
-
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 -
Mindestens zwei Overloads
- Ein einzelner Overload macht keinen Sinn
- Nutze stattdessen Union Types
-
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!