ProgrammazioneSviluppatore Frontend

Come implementare e tipizzare una funzione che restituisce un tipo union o un tuple a seconda dei parametri? Come descrivere correttamente i valori restituiti per non perdere la tipizzazione rigorosa durante le operazioni future e l'uso della destrutturazione?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • Utilizzare overload per funzioni che restituiscono diversi tipi a seconda dei parametri.
  • Per tuple, utilizzare sempre [T, U] o as const per mantenere la lunghezza e il tipo esatti degli elementi dopo la destrutturazione.
  • I tipi union dei valori restituiti richiedono type guards per una definizione ristretta del tipo durante il lavoro successivo.

Domande trabocchetto.

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

Errori tipici e anti-pattterns

  • Descrivere la funzione come restitutrice di any[] o (T | U)[], che porta a una "sfocatura" della specificità della tupla.
  • Tentare di utilizzare la destrutturazione senza type narrowing e controlli di validità del tipo degli elementi.
  • Mancanza di una descrizione diretta della struttura del valore restituito: complessità di supporto e completamento automatico.

Esempio dalla vita reale

Caso negativo

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:

  • Codice semplice da scrivere senza tipizzazione.

Contro:

  • Errore solo a runtime.
  • Perdita di tipizzazione rigorosa, più bug.

Caso positivo

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:

  • Prevedibilità e chiarezza del codice.
  • Completamento automatico per le tuple.

Contro:

  • Necessità di descrivere i tipi manualmente.
  • Aumenta il numero di type guards e controlli.