Function Overloads
Lerne wie du mit Function Overloads verschiedene Aufrufvarianten einer Funktion typisierst.
Inhaltsverzeichnis
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!