Utility Types avanzati in TypeScript

Nella lezione precedente abbiamo visto alcuni Utility Types che permettono di modificare la struttura di un tipo esistente. TypeScript ne mette a disposizione molti altri, utili per creare tipi a partire da oggetti, funzioni e unioni di tipi.

In questa lezione vedremo Record, ReturnType, Parameters, Exclude ed Extract.

Tipo Descrizione
Record Crea un tipo con proprietà definite.
ReturnType Ricava il tipo restituito da una funzione.
Parameters Ricava i tipi dei parametri di una funzione.
Exclude Esclude tipi da un'unione.
Extract Seleziona tipi da un'unione.

Vediamoli in dettaglio.

Record<K, T>

Record<K, T> permette di creare un tipo oggetto in cui:

  • K rappresenta il tipo delle chiavi.
  • T rappresenta il tipo dei valori.

Ad esempio, possiamo creare un tipo Utenti:

type Utenti = Record<string, string>;

Questo tipo rappresenta un oggetto con chiavi di tipo string e valori anch'essi di tipo string.

Possiamo quindi scrivere:

const utenti: Utenti = {
    mario: "Andrea Rossi",
    luca: "Luca Bianchi",
    anna: "Anna Verdi"
};

Record con chiavi specifiche

K non deve necessariamente essere string. Possiamo utilizzare un'unione di tipi per definire un insieme preciso di chiavi. Ad esempio:

type Giorno = "lunedi" | "martedi" | "mercoledi";

type Orari = Record<Giorno, string>;

Ora un oggetto Orari deve contenere tutte queste chiavi:

const orari: Orari = {
    lunedi: "09:00",
    martedi: "10:00",
    mercoledi: "09:30"
};

Questo rende Record particolarmente utile quando vogliamo associare un insieme noto di chiavi a valori dello stesso tipo.

ReturnType<T>

ReturnType<T> permette di ottenere automaticamente il tipo restituito da una funzione.

Supponiamo di avere una funzione creaUtente():

function creaUtente() {
    return {
        id: 1,
        nome: "Andrea"
    };
}

Possiamo utilizzare:

type Utente = ReturnType<typeof creaUtente>;

Il risultato sarà equivalente a:

type Utente = {
    id: number;
    nome: string;
};

In questo modo non dobbiamo riscrivere manualmente il tipo restituito dalla funzione.

Utilizzare typeof

ReturnType riceve come parametro il tipo della funzione, non il suo risultato.

Per ottenere il tipo della funzione creaUtente utilizziamo typeof creaUtente e quindi ReturnType<typeof creaUtente>.

Possiamo utilizzare il tipo ottenuto normalmente:

const utente: Utente = {
    id: 2,
    nome: "Luca"
};

Se in futuro cambiamo il valore restituito dalla funzione, il tipo generato da ReturnType si aggiornerà automaticamente.

Parameters<T>

Parameters<T> permette di estrarre i tipi dei parametri di una funzione e di ottenere una tupla.

Ad esempio:

function creaUtente(nome: string, eta: number) {
    console.log(nome, eta);
}

Possiamo ottenere i tipi dei suoi parametri con:

type ParametriUtente = Parameters<typeof creaUtente>;

Il risultato sarà equivalente a:

type ParametriUtente = [nome: string, eta: number];

In pratica, Parameters ci restituisce una tupla che contiene i tipi dei parametri nello stesso ordine in cui sono definiti nella funzione.

Utilizzare la tupla ottenuta

Possiamo utilizzare il tipo generato per descrivere un array o tupla compatibile con i parametri della funzione:

const parametri: ParametriUtente = ["Andrea", 30];

Usare invece [30, "Marco"] genera un errore perché i tipi sono nell'ordine sbagliato.

Exclude<T, U>

Exclude<T, U> permette di rimuovere da un'unione tutti i tipi compatibili con U.

Per esempio:

type Colore = "rosso" | "verde" | "blu";

Possiamo eliminare "verde":

type AltriColori = Exclude<Colore, "verde">;

Il risultato sarà:

type AltriColori = "rosso" | "blu";

Possiamo quindi scrivere:

let colore: AltriColori;

colore = "rosso";
colore = "blu";

Se invece scriviamo colore = "verde", il codice genererà un errore.

Exclude con più tipi

Possiamo rimuovere più elementi contemporaneamente:

type Colore = "rosso" | "verde" | "blu" | "giallo";

type ColoriPrimari = Exclude<Colore, "verde" | "giallo">;

Il risultato sarà:

type ColoriPrimari = "rosso" | "blu";

Exclude è quindi utile quando vogliamo creare una nuova unione di tipi partendo da una esistente e rimuovendo determinati valori.

Extract<T, U>

Extract<T, U> esegue l'operazione inversa rispetto a Exclude: invece di rimuovere i tipi compatibili con U, mantiene soltanto quelli compatibili.

Ad esempio:

type Tipo = string | number | boolean;

Possiamo estrarre soltanto string e number:

type StringaONumero = Extract<Tipo, string | number>;

Il risultato sarà:

type StringaONumero = string | number;

Il boolean viene escluso perché non è compatibile con string | number.

Extract con tipi letterali

Possiamo utilizzare Extract anche con un'unione di valori letterali:

type Stato = "attivo" | "inattivo" | "sospeso";

type StatoAttivo = Extract<Stato, "attivo" | "sospeso">;

Il risultato diventerà:

type StatoAttivo = "attivo" | "sospeso";

Possiamo quindi utilizzare StatoAttivo per accettare soltanto quei valori:

let stato: StatoAttivo;

stato = "attivo";
stato = "sospeso";

Combinare gli Utility Types

Gli Utility Types possono essere combinati per creare tipi più complessi.

Per esempio, possiamo utilizzare ReturnType insieme a Partial:

function creaUtente() {
    return {
        id: 1,
        nome: "Andrea",
        email: "[email protected]"
    };
}

type Utente = ReturnType<typeof creaUtente>;

type ModificaUtente = Partial<Utente>;

Utente viene ricavato automaticamente dal valore restituito dalla funzione, mentre Partial ne crea una versione con tutte le proprietà opzionali.

Possiamo quindi scrivere:

const modifiche: ModificaUtente = {
    email: "[email protected]"
};

In questo modo, non abbiamo dovuto duplicare la struttura dell'utente.

Prova!Completa gli spazi vuoti con il testo appropriato.
// Mantieni solo i tipi compatibili indicati
type Frutto = "mela" | "pera" | "banana" | "kiwi";

type FruttiTropicali = <Frutto, "banana" | "kiwi">;

let frutto: FruttiTropicali;
frutto = "banana";

Prova! Scegli l'opzione corretta tra quelle elencate.
// Scegli l'Utility Type corretta
function registra(utente: string, eta: number, attivo: boolean) {
    console.log(utente, eta, attivo);
}

type Parametri = _________<typeof registra>;

const dati: Parametri = ["Andrea", 30, true];