Storia della domanda:
In alcuni casi, è necessario che una funzione possa restituire diverse strutture dati, ad esempio, un array di lunghezza fissa (tuple) o diversi risultati (tipo union). In JavaScript dinamico, questo approccio è ampiamente utilizzato per modelli "risultato o errore" (ad esempio, [err, value]), mentre in TypeScript richiede una chiara tipizzazione affinché il codice successivo comprenda correttamente la struttura del risultato.
Problema:
Senza una chiara assegnazione del tipo restituito, TypeScript porta il risultato a una forma troppo "ampia" (cioè non specifica) - un array o un union, che priva lo sviluppatore dei vantaggi del completamento automatico e della sicurezza dei tipi. Se si destruttura il risultato della funzione, c'è il rischio di perdere la descrizione precisa dei tipi degli elementi.
Soluzione:
È necessario utilizzare tipi condizionali e dichiarazioni di overload per le funzioni, e per le tuple - specificare esplicitamente il tipo dell'array restituito e utilizzare as const (se restituiamo una tupla costante). Per i risultati union, è possibile combinare generics e type narrowing per poter distinguere correttamente il tipo del risultato restituito anche al di fuori del codice.
Esempio di codice:
type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Invalid number'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // Con tuple e as const per strutture auto-varie: 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] }
Caratteristiche chiave:
Il tipo dell'elemento dell'array dopo la destrutturazione di un union restituito da una tupla sarà sempre specifico?
No, se una funzione restituisce union di tuple, dopo la destrutturazione gli elementi rappresenteranno unione di tutti i possibili tipi variabili.
type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string
È possibile "fissare" completamente il tipo di una tupla con as const senza una dichiarazione di tipo addizionale nella funzione?
No, as const fissa i valori, ma se la funzione è dichiarata senza un tipo di ritorno, TypeScript potrebbe dedurre un tipo più ampio del necessario. È meglio specificare esplicitamente il tipo di ritorno.
function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]
Le overload con diversi tipi di ritorno garantiscono una definizione certa del tipo del risultato dopo la destrutturazione?
Le overload aiutano il compilatore, ma se il parametro d'ingresso è sconosciuto, il risultato sarà un'unione di tutte le opzioni. Per un restringimento preciso del tipo, è necessario utilizzare type guards nel gestore del risultato.
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
La funzione restituisce un array o un errore, ma non lo descrive esplicitamente nella firma di tipo. Nel codice chiamante si aspetta che il risultato sia un numero, e l'accesso a result[1].toFixed(2] genera un errore a runtime.
Pro:
Contro:
La funzione restituisce un tuple strettamente tipizzato con tipo Result<T>, la gestione del risultato si basa sulla destrutturazione e su un'esplicita verifica runtime del primo elemento (error/null). Il compilatore garantisce che si possa accedere al risultato solo dopo un corretto restringimento del tipo.
Pro:
Contro: