Generics in interfacce e alias di tipo in TypeScript

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.

Le interfacce generiche

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.

Utilizzare tipi diversi

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.

Più parametri generici

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.

Alias di tipi generici

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
};

Generics e unioni di tipi

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.

Valori predefiniti per i parametri generici

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"
};

Sovrascrivere il tipo predefinito

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.

Prova!Completa gli spazi vuoti con il testo appropriato.
// Definisci l'alias di tipo generico correttamente
type PacchettoDati = {
    codiceStato: number;
    carico: T;
};

Prova! Scegli l'opzione corretta tra quelle elencate.
interface RispostaStato {
    ok: boolean;
    valore: T;
}

const report: RispostaStato<______> = {
    ok: true,
    valore: false
};