I Generics possono essere utilizzati non soltanto nelle funzioni. TypeScript permette di applicarli anche a interfacce e alias di tipo, rendendo possibile definire strutture riutilizzabili con tipi diversi.
Questo è particolarmente utile quando una stessa struttura deve essere utilizzata con dati di natura differente. Un esempio comune è la risposta di un'API: la struttura della risposta può essere sempre la stessa, mentre i dati contenuti possono cambiare.
Un'interfaccia può ricevere uno o più parametri generici. Ad esempio:
interface ApiResponse<T> {
success: boolean;
data: T;
}Nell'esempio, T rappresenta il tipo dei dati contenuti nella proprietà data.
Possiamo quindi utilizzare la stessa interfaccia con tipi differenti. Ad esempio, per una risposta contenente un utente:
interface Utente {
id: number;
nome: string;
}
const risposta: ApiResponse<Utente> = {
success: true,
data: {
id: 1,
nome: "Andrea"
}
};In questo caso T viene sostituito da Utente. Di conseguenza, TypeScript sa che risposta.data è un oggetto di tipo Utente.
La stessa interfaccia può essere utilizzata con un qualsiasi tipo di valore, ad esempio una string:
const risposta: ApiResponse<string> = {
success: true,
data: "Operazione completata"
};Oppure con un array:
const risposta: ApiResponse<Utente[]> = {
success: true,
data: [
{
id: 1,
nome: "Andrea"
},
{
id: 2,
nome: "Luca"
}
]
};In questo caso T è Utente[].
La struttura dell'interfaccia rimane invariata, mentre cambia il tipo di data.
Un'interfaccia può avere più parametri generici. Per esempio:
interface Risposta<T, E> {
data: T;
errore: E;
}Possiamo specificare separatamente il tipo dei dati e quello dell'errore:
const risposta: Risposta<Utente, string> = {
data: {
id: 1,
nome: "Andrea"
},
errore: ""
};In questo caso T è di tipo Utente, mentre E è di tipo string.
I parametri generici permettono quindi di mantenere separate le informazioni sui diversi tipi utilizzati dalla struttura.
Anche gli alias di tipi possono ricevere parametri generici.
La sintassi è simile a quella delle interfacce:
type Risultato<T> = {
valore: T;
valido: boolean;
};Possiamo utilizzare l'alias di tipo con tipi differenti:
const risultatoNumero: Risultato<number> = {
valore: 100,
valido: true
};I parametri generici possono essere combinati con altri tipi, comprese le unioni di tipi. Per esempio:
type Risposta<T> = {
data: T | null;
errore: string | null;
};Possiamo utilizzarlo con un utente:
const risposta: Risposta<Utente> = {
data: {
id: 1,
nome: "Andrea"
},
errore: null
};In questo caso data può essere un Utente oppure null.
I Generics non sostituiscono quindi gli altri strumenti del sistema di tipi: possono essere combinati con unione di tipi, intersezione di tipi, alias di tipi e interfacce.
Un parametro generico può avere un tipo predefinito.
La sintassi da usare è: <T = tipo>.
Ad esempio, possiamo scrivere:
interface ApiResponse<T = string> {
success: boolean;
data: T;
}Se non specifichiamo T, TypeScript utilizzerà string come valore predefinito:
const risposta: ApiResponse = {
success: true,
data: "Operazione completata"
};L'esempio è equivalente a scrivere in questo modo:
const risposta: ApiResponse<string> = {
success: true,
data: "Operazione completata"
};Il tipo predefinito non è obbligatorio: possiamo sostituirlo specificando esplicitamente un altro tipo.
Per esempio:
const risposta: ApiResponse<number> = {
success: true,
data: 200
};In questo caso T non è più string, ma number.
Il valore predefinito viene quindi utilizzato soltanto quando non viene specificato un tipo diverso.