L'overloading di funzioni in TypeScript consiste nello scrivere più firme di chiamata per una funzione, seguite da un'unica implementazione. Ogni firma può abbinare tipi di parametro diversi a un tipo di ritorno diverso, e chi chiama ottiene quello preciso.
Senza gli overload, parse restituirebbe number | number[] per ogni chiamata e one + 1 sarebbe un errore finché non restringi tu il risultato.
Overload signatures e implementazione
Una funzione con overload ha due parti:
- Overload signatures: dichiarazioni senza corpo, una per ogni forma di chiamata supportata. Sono le uniche firme che chi chiama può usare.
- La firma di implementazione: l'ultima dichiarazione, quella con il corpo. I suoi parametri devono accettare tutto ciò che accettano gli overload, e il suo tipo di ritorno deve coprire il tipo di ritorno di ogni overload. Dall'esterno è invisibile.
I tipi esistono solo in fase di compilazione, quindi a runtime c'è una sola funzione JavaScript. L'implementazione deve esaminare i suoi argomenti (typeof, Array.isArray, arguments.length...) per decidere cosa fare. Il compilatore controlla che overload e implementazione siano d'accordo:
function format(value: string): string;
function format(value: number): number {
return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.
La soluzione è allargare l'implementazione: function format(value: string | number): string | number.
La firma di implementazione non si può chiamare
È la regola che sorprende di più. Una chiamata deve corrispondere da sola a una delle overload signatures; TypeScript non le combina.
Il compilatore stampa:
index.ts(12,19): error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
Type 'string' is not assignable to type 'string[]'.
L'implementazione accetta string | string[], ma chi chiama non la vede. Aggiungi un terzo overload che prende l'unione e restituisce l'unione, e la chiamata compila e stampa [ 1, 2 ]:
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
return Array.isArray(input) ? input.map(Number) : Number(input);
}
Numeri diversi di parametri
Gli overload descrivono anche chiamate con un numero diverso di argomenti. Qui una data si può costruire da un timestamp oppure da anno, mese e giorno, ma non da due numeri:
Una sola firma con due parametri facoltativi accetterebbe makeDate(2024, 3) e costruirebbe in silenzio la data sbagliata. Gli overload lo trasformano in un errore di compilazione (TS2575).
L'ordine conta
TypeScript prova gli overload dall'alto verso il basso e sceglie il primo che corrisponde. Metti prima le firme più specifiche. Un overload generico all'inizio della lista si prende le chiamate destinate a quelli successivi:
function describe(value: unknown): string; // matches everything
function describe(value: string): "text"; // never chosen
function describe(value: unknown): string {
return typeof value === "string" ? "text" : "other";
}
const d = describe("hi"); // d: string, not "text"
Scambia le prime due firme e describe("hi") avrà tipo "text".
Overload o parametro unione?
Gli overload valgono le righe in più quando il tipo di ritorno dipende dai tipi degli argomenti. Quando non è così, una sola firma con un parametro unione è più corta, più leggibile e accetta argomenti unione che gli overload rifiuterebbero.
| Usa | Quando |
|---|---|
| Un parametro unione | Stesso tipo di ritorno per ogni input |
| Parametri facoltativi | Le forme di chiamata differiscono solo per argomenti finali che si possono omettere liberamente |
| Overload | Il tipo di ritorno cambia con gli argomenti, oppure alcune combinazioni di argomenti vanno rifiutate |
| Un generic | Il tipo di ritorno è costruito dal tipo dell'argomento, come identity<T>(x: T): T |
Un generic con un conditional type può esprimere alcuni insiemi di overload con una sola firma, ma per due o tre casi gli overload di solito sono più leggibili.
Metodi e costruttori con overload
Dentro una classe i metodi usano lo stesso schema: overload signatures, poi il metodo con il corpo. Anche i costruttori si possono sovraccaricare allo stesso modo.
Anche interfacce e tipi oggetto possono dichiarare overload, come più firme di chiamata o più firme di metodo con lo stesso nome. Molte funzioni integrate sono dichiarate così: passa il mouse su reduce di un array in un editor e vedrai "+2 overloads".
Domande frequenti
TypeScript supporta l'overloading delle funzioni?
Sì, a livello di tipi. Scrivi più overload signatures (dichiarazioni senza corpo) seguite da una sola implementazione. Chi chiama vede solo le overload signatures. A runtime esiste comunque una sola funzione JavaScript, quindi l'implementazione controlla da sé gli argomenti e gestisce ogni caso.
Cosa significa "No overload matches this call"?
Errore TS2769: gli argomenti non corrispondono a nessuna delle overload signatures. La firma di implementazione non conta, quindi una chiamata con un argomento unione come string | string[] fallisce anche se l'implementazione lo accetta. Aggiungi un overload che prende l'unione, oppure sostituisci gli overload con una sola firma.
Quando usare gli overload invece di un tipo unione?
Usa gli overload quando il tipo di ritorno dipende dai tipi degli argomenti passati, per esempio string in ingresso dà number in uscita ma string[] in ingresso dà number[] in uscita. Quando il tipo di ritorno è lo stesso per ogni input, una sola firma con un parametro unione è più semplice e accetta anche argomenti unione.
Si possono fare overload delle arrow function in TypeScript?
Non con la sintassi di dichiarazione degli overload, che funziona solo per le dichiarazioni function e per i metodi. Puoi dare a una variabile un tipo con più firme di chiamata, type Parse = { (s: string): number; (s: string[]): number[] }, ma assegnarle una arrow function di solito richiede una type assertion, quindi una dichiarazione function è la scelta più pulita.
Perché la mia overload signature non è compatibile con la firma di implementazione?
L'errore TS2394 significa che un overload accetta o restituisce qualcosa che l'implementazione non gestisce. I parametri dell'implementazione devono accettare i parametri di ogni overload, e il suo tipo di ritorno deve essere compatibile con il tipo di ritorno di ogni overload. Allargare l'implementazione (spesso a un'unione) risolve il problema.