Die Geschichte der Frage:
In einigen Aufgaben ist es erforderlich, dass eine Funktion verschiedene Datenstrukturen zurückgeben kann – zum Beispiel ein Array fester Länge (Tuple) oder mehrere Ergebnisoptionen (Union-Typ). In dynamischem JavaScript wird dieser Ansatz häufig für die „Ergebnis oder Fehler“-Muster verwendet (z. B. [err, value]), während TypeScript eine klare Typisierung erfordert, damit der nachfolgende Code die Struktur des Ergebnisses korrekt versteht.
Das Problem:
Ohne die explizite Angabe des Rückgabetyps führt TypeScript das Ergebnis in eine zu „breite“ (d. h. nicht enge) Form — ein Array oder einen Union-Typ, was den Entwicklern die Vorteile von Autovervollständigung und Typsicherheit entzieht. Wenn das Ergebnis der Funktion destrukturiert wird, besteht das Risiko, dass die genaue Beschreibung der Elementtypen verloren geht.
Die Lösung:
Es ist notwendig, bedingte Typen und Überladebeschreibungen von Funktionen zu verwenden, und für Tuples sollte der Typ des Rückgabearrays explizit angegeben werden und as const verwendet werden (falls ein konstanter Tuple zurückgegeben wird). Für Union-Ergebnisse kann man Generics und Type Narrowing kombinieren, um den Rückgabewert korrekt zu differenzieren.
Beispielcode:
type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Ungültige Zahl'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // Mit Tuple und as const für auto-erzwingende Strukturen: function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'error'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }
Wichtige Merkmale:
as const verwenden, um die genaue Länge und den Typ der Elemente nach der Destrukturierung beizubehalten.Wird der Typ des Array-Elements nach der Destrukturierung eines zurückgegebenen Tuples immer spezifisch sein?
Nein, wenn die Funktion Union von Tuples zurückgibt, werden die Elemente nach der Destrukturierung eine Union aller möglichen Variationen darstellen.
type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string
Kann man mit as const den Typ des Tuples ohne zusätzliche Typbeschreibung der Funktion vollständig "fixieren"?
Nein, as const fixiert die Werte, aber wenn die Funktion ohne Rückgabetypsignatur deklariert wird, kann TypeScript den Typ breiter ableiten, als nötig. Es ist besser, den Rückgabewert explizit anzugeben.
function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]
Garantieren Überladungen mit verschiedenen Rückgabetypen eine genaue Typenbestimmung des Ergebnisses nach der Destrukturierung?
Überladungen helfen dem Compiler, aber wenn der Eingabeparameter unbekannt ist, wird das Ergebnis eine Union aller Optionen sein. Für eine genaue Typenverfeinerung müssen Type Guards im Ergebnis-Handler verwendet werden.
function bar(x: number): string; function bar(x: boolean): number; function bar(x: any): any { return typeof x === 'number' ? 'str' : 123; } const r = bar(Math.random() > 0.5 ? true : 1); // r: number | string
any[] oder (T | U)[] zu beschreiben, was zu einer „Verschwommenheit“ der Tuple-Spezifikationen führt.Eine Funktion gibt entweder ein Array oder einen Fehler zurück, beschreibt dies jedoch nicht explizit in der Typ-Signatur. Im aufrufenden Code wird erwartet, dass das Ergebnis eine Zahl ist, und der Zugriff auf result[1].toFixed(2) wirft zur Laufzeit einen Fehler.
Vorteile:
Nachteile:
Die Funktion gibt einen streng typisierten Tuple vom Typ Result<T> zurück, die Verarbeitung des Ergebnisses basiert auf Destrukturierung und expliziter Runtime-Prüfung des ersten Elements (error/null). Der Compiler garantiert, dass auf das Ergebnis nur bei erfolgreicher Typenverfeinerung zugegriffen werden kann.
Vorteile:
Nachteile: