Menu

Generics in TypeScript: funzioni, tipi e classi generiche

I generics permettono a una funzione, interfaccia, tipo o classe di lavorare con molti tipi mantenendoli collegati: ciò che entra decide ciò che esce. Impara le funzioni generiche, la deduzione degli argomenti di tipo, più parametri di tipo, interfacce e classi generiche, i default e quando non usare i generics.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

I generics sono parametri di tipo. Una funzione generica dichiara un tipo segnaposto, di solito chiamato T, e ogni chiamata lo riempie, così i tipi che entrano decidono i tipi che escono. Una sola funzione funziona allora con numeri, stringhe e i tuoi oggetti, e ogni chiamata resta tipizzata al completo.

<T> dopo il nome della funzione dichiara il parametro di tipo. items: T[] lo usa per l'input e T | undefined per l'output, e questo lega i due. Non hai mai scritto number o string: TypeScript ha dedotto T dall'argomento.

Perché non any o unknown?

Senza generics tipizzeresti il parametro come any o unknown. Entrambi accettano qualsiasi array, ed entrambi perdono il legame tra input e output:

Tipo del parametroAccetta ogni inputTipo del risultatoControllato
anysìanyno: gli errori compilano
unknownsìunknownsì, ma devi restringerlo prima di usarlo
T (generic)sìlegato all'inputsì, con il tipo preciso

Deduzione degli argomenti di tipo

Di solito TypeScript deduce gli argomenti di tipo dai valori che passi. Puoi anche scriverli in modo esplicito tra parentesi angolari nella chiamata, cosa necessaria quando niente negli argomenti menziona T, o quando la deduzione sceglie qualcosa che non volevi.

Vale la pena conoscere due risultati della deduzione. pair(1, "one") senza l'argomento esplicito è un errore: TypeScript deduce T = number dal primo argomento e poi segnala Argument of type 'string' is not assignable to parameter of type 'number'. (TS2345). E emptyList() senza alcun argomento ottiene T = unknown, cioè unknown[], che raramente è ciò che vuoi.

Più parametri di tipo

Una funzione può dichiarare tutti i parametri di tipo che le servono, separati da virgole. Ciascuno viene dedotto in modo indipendente.

mapValues deduce tre cose insieme: K è "tea" | "cake", V è number e R è string dal valore restituito dalla callback. K extends string è un vincolo, spiegato nella pagina successiva.

Interfacce e alias di tipo generici

Anche i tipi possono avere parametri. Scrivi allora l'argomento quando usi il tipo: Box<number>, ApiResponse<User>. La maggior parte dei tipi di collezione integrati funziona così: Array<T>, Map<K, V>, Promise<T>, Record<K, V>.

Result<T, E = string> mostra anche un parametro di tipo di default: Result<number> significa Result<number, string>. Come i parametri facoltativi delle funzioni, i parametri con default devono venire dopo quelli obbligatori.

Classi generiche

Una classe riceve i parametri di tipo dopo il suo nome, e ogni istanza li fissa. Campi, metodi e parametri del costruttore possono usarli tutti.

Se il costruttore prende un T, puoi omettere l'argomento esplicito: new Box(5) deduce Box<number>. I membri statici appartengono alla classe stessa, non a un'istanza, quindi non possono usare il parametro di tipo della classe (Static members cannot reference class type parameters., TS2302).

Arrow function generiche e tipi funzione

La lista dei parametri di tipo va prima della lista dei parametri. La stessa sintassi descrive un tipo funzione generico.

const last = <T>(items: T[]): T | undefined => items[items.length - 1];

type Mapper = <T, R>(items: T[], fn: (item: T) => R) => R[];
const mapAll: Mapper = (items, fn) => items.map(fn);

// In a .tsx file, <T> looks like a JSX tag. Add a trailing comma:
const lastTsx = <T,>(items: T[]) => items[items.length - 1];

Dedurre tipi letterali con i const type parameter

Di default un generic deduce tipi allargati: pair("a", "b") dà [string, string]. Quando contano i valori esatti (nomi di route, nomi di eventi, elenchi di colonne), marca il parametro come const (da TypeScript 5.0 in poi) e la deduzione avviene come se l'argomento fosse scritto as const:

Chi chiama scrive un normale array, e la funzione conserva i tipi letterali.

Quando non usare i generics

Un parametro di tipo si guadagna il posto quando collega due cose: un parametro e il tipo di ritorno, due parametri, oppure un parametro e una callback. Se T compare una sola volta non aggiunge nulla, e un tipo semplice è più chiaro.

// Pointless: T is used once, so it is just a longer way to write unknown
function logValue<T>(value: T): void {
  console.log(value);
}

// Clearer
function logValueSimple(value: unknown): void {
  console.log(value);
}

// Also pointless: returns T but nothing connects T to an argument,
// so the caller is really just asserting a type
function parseJson<T>(text: string): T {
  return JSON.parse(text);
}

L'ultimo caso è comune e ingannevole: parseJson<User>(text) sembra type-safe ma non controlla nulla, esattamente come JSON.parse(text) as User. Restituisci invece unknown e valida il dato. Altri segnali di abuso: un parametro di tipo che riceve sempre lo stesso argomento, o uno che si potrebbe sostituire con un'unione di due tipi noti.

Per i generics che devono sapere qualcosa di T (che ha un length, o che K è una chiave di T), il passo successivo sono i vincoli con extends.

Domande frequenti

Cosa sono i generics in TypeScript?

I generics sono parametri di tipo: segnaposto come T che vengono riempiti con un tipo reale ogni volta che si usa una funzione, un'interfaccia, un alias di tipo o una classe. function first<T>(items: T[]): T | undefined funziona con qualsiasi array, e il risultato ha il tipo degli elementi dell'array che hai passato, quindi first([1, 2]) è number | undefined e first(["a"]) è string | undefined.

Che differenza c'è tra generics e any?

any disattiva il controllo dei tipi: il valore che entra e quello che esce non hanno alcun legame, e anche il risultato è any. Un generic mantiene il legame: identity<T>(x: T): T restituisce esattamente il tipo che hai passato. Usa unknown se accetti qualsiasi cosa ma non devi restituire il tipo, e un generic se invece devi.

Cosa significa <T> in TypeScript?

Dichiara un parametro di tipo chiamato T. In function wrap<T>(value: T), T è una variabile di tipo che TypeScript riempie a partire dall'argomento a ogni chiamata, oppure che passi in modo esplicito come wrap<string>("a"). Il nome T è solo una convenzione; nomi più lunghi come TItem o Key funzionano allo stesso modo.

Come si scrive una arrow function generica in TypeScript?

Metti la lista dei parametri di tipo prima dei parametri: const first = <T>(items: T[]): T | undefined => items[0];. In un file .tsx <T> verrebbe letto come un tag JSX, quindi scrivi <T,> con una virgola finale, oppure <T extends unknown>.

Come si imposta un tipo di default per un generic in TypeScript?

Aggiungi = Type dopo il parametro: interface ApiResponse<T = unknown> { data: T }. Allora ApiResponse senza argomento significa ApiResponse<unknown>. I parametri con default devono venire dopo quelli senza, come i parametri facoltativi delle funzioni.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA