Introduzione ai Generics in TypeScript

I Generics permettono di creare funzioni, classi e altri componenti riutilizzabili che possono lavorare con tipi diversi, mantenendo allo stesso tempo i controlli di tipo di TypeScript.

Senza i Generics, per rendere una funzione utilizzabile con valori di tipo diverso potremmo essere tentati di utilizzare any. Questo però elimina gran parte dei vantaggi della tipizzazione statica.

I Generics permettono di specificare che il tipo verrà definito in modo preciso al momento dell'utilizzo del componente.

Come utilizzare i Generics

Prima di vedere come definire tipi generici, vediamo perché in alcuni casi è necessario utilizzarli.

Supponiamo di voler creare una funzione che restituisca il valore ricevuto:

function restituisci(valore: any): any {
    return valore;
}

Questa funzione può ricevere valori di qualsiasi tipo:

const numero = restituisci(10);
const testo = restituisci("Ciao");

Il problema è che any disattiva i controlli di tipo, essendo un tipo di valore sconosciuto. TypeScript non riesce quindi a mantenere una relazione precisa tra il valore passato alla funzione e quello restituito.

Possiamo ottenere lo stesso risultato utilizzando un Generic:

function restituisci<T>(valore: T): T {
    return valore;
}

In questo caso T rappresenta un tipo generico che verrà determinato quando la funzione viene utilizzata.

Se passiamo un numero, TypeScript dedurrà che T è un number:

const numero = restituisci(10);

Allo stesso modo, se passiamo una stringa, TypeScript dedurrà che T è una string.

La type variable <T>

La lettera T è comunemente utilizzata per indicare una Type Variable, cioè una variabile che rappresenta un tipo.

Ad esempio:

function restituisci<T>(valore: T): T {
    return valore;
}

La parte <T> dichiara il parametro di tipo T. Il parametro viene poi utilizzato sia per il parametro della funzione valore: T sia per il suo valore di ritorno : T.

In questo modo TypeScript mantiene il collegamento tra il tipo del valore ricevuto e quello del valore restituito.

Specificare esplicitamente il tipo

Nella maggior parte dei casi non è necessario specificare manualmente il tipo generico. Ad esempio:

function primoElemento<T>(array: T[]): T {
    return array[0];
}

Possiamo chiamare la funzione passando un array di numeri:

const numero = primoElemento([10, 20, 30]);

TypeScript deduce automaticamente T = number e, di conseguenza, numero viene considerato un number.

È però possibile indicare manualmente il parametro generico. Per capire come farlo, utilizzando la funzione:

function restituisci<T>(valore: T): T {
    return valore;
}

Possiamo specificare T direttamente:

const numero = restituisci<number>(10);

In questo caso stiamo dicendo esplicitamente a TypeScript che T = number.

Mantenere la relazione tra i tipi

Uno dei principali vantaggi dei Generics è che permettono di mantenere una relazione tra diversi elementi di una funzione.

Consideriamo, ad esempio:

function creaCoppia<T>(valore: T): [T, T] {
    return [valore, valore];
}

Passiamo ora un numero alla funzione:

const coppia = creaCoppia(10);

Nell'esempio, TypeScript sa che il risultato è [number, number].

Il tipo T permette quindi di mantenere la relazione tra il parametro ricevuto e il risultato prodotto.

Più parametri generici

Una funzione può avere più parametri di tipo. Ad esempio:

function creaCoppia<T, U>(primo: T, secondo: U): [T, U] {
    return [primo, secondo];
}

In questo caso abbiamo due type variable <T, U>: T rappresenta il tipo del primo parametro, mentre U rappresenta quello del secondo.

Possiamo utilizzarla così:

const coppia = creaCoppia("Andrea", 30);

TypeScript deduce automaticamente che T è di tipo string e U è di tipo number. Di conseguenza, coppia viene considerata [string, number].

Prova!Completa gli spazi vuoti con il testo appropriato.
// La funzione deve accettare un array di elementi di tipo T
function ultimoElemento(elenco: ): T {
    return elenco[elenco.length - 1];
}

Prova! Scegli l'opzione corretta tra quelle elencate.
// La funzione deve restituire una tupla
function combina(a: T, b: U): ______ {
    return [a, b];
}