I principali Utility Types in TypeScript

TypeScript mette a disposizione diversi Utility Types già inclusi nel linguaggio. Si tratta di tipi generici che permettono di trasformare tipi esistenti senza doverne riscrivere manualmente la struttura.

Sono particolarmente utili quando abbiamo già definito un tipo o un'interfaccia e vogliamo crearne una variante, ad esempio rendendo alcune proprietà opzionali, impedendone la modifica oppure selezionando soltanto alcune proprietà.

In questa lezione vedremo Partial, Required, Readonly, Pick e Omit.

Tipo Descrizione
Partial Rende tutte le proprietà opzionali.
Required Rende tutte le proprietà obbligatorie.
Readonly Rende tutte le proprietà non modificabili.
Pick Seleziona specifiche proprietà.
Omit Esclude specifiche proprietà.

Vediamo ciascun Utility Type in dettaglio.

Partial<T>

Partial<T> trasforma tutte le proprietà di un tipo in proprietà opzionali.

Ad esempio, supponiamo di avere:

interface Utente {
    nome: string;
    email: string;
    eta: number;
}

Normalmente, quando creiamo un Utente, tutte le proprietà sono obbligatorie:

const utente: Utente = {
    nome: "Andrea",
    email: "[email protected]",
    eta: 30
};

Possiamo utilizzare Partial<Utente> per creare un tipo in cui tutte le proprietà sono opzionali:

const modifiche: Partial<Utente> = {
    nome: "Luca"
};

Possiamo quindi specificare anche soltanto una proprietà, oppure nessuna:

const modifiche1: Partial<Utente> = {
    eta: 31
};

const modifiche2: Partial<Utente> = {};

Tutte queste situazioni sono valide perché Partial rende opzionali tutte le proprietà.

Rendere opzionali specifiche proprietà

Partial è particolarmente utile nelle funzioni che devono aggiornare soltanto alcune proprietà di un oggetto. Ad esempio:

function aggiornaUtente(
    utente: Utente,
    modifiche: Partial<Utente>
): void {
    Object.assign(utente, modifiche);
}

In questo esempio, usiamo Object.assign() perché permette di copiare le proprietà di modifiche dentro utente, aggiornando così soltanto i valori forniti.

Possiamo quindi modificare soltanto l'email:

aggiornaUtente(utente, {
    email: "[email protected]"
});

Non è più necessario specificare tutte le proprietà di Utente.

Required<T>

Required<T> esegue l'operazione opposta rispetto a Partial<T>: trasforma tutte le proprietà opzionali di un tipo in proprietà obbligatorie.

Ad esempio:

interface Utente {
    nome: string;
    email?: string;
    eta?: number;
}

Normalmente possiamo creare un utente senza email e eta:

const utente: Utente = {
    nome: "Andrea"
};

Utilizzando Required<Utente>, tutte le proprietà diventano obbligatorie:

const utenteCompleto: Required<Utente> = {
    nome: "Andrea",
    email: "[email protected]",
    eta: 30
};

Readonly<T>

Readonly<T> rende tutte le proprietà di un tipo non riassegnabili.

Consideriamo, ad esempio, questa interfaccia:

interface Prodotto {
    nome: string;
    prezzo: number;
}

Normalmente possiamo modificare le proprietà in questo modo:

const prodotto: Prodotto = {
    nome: "Tastiera",
    prezzo: 50
};

prodotto.prezzo = 45;

Utilizzando Readonly<Prodotto>, non possiamo più riassegnare le proprietà:

const prodotto: Readonly<Prodotto> = {
    nome: "Tastiera",
    prezzo: 50
};

Ad esempio, se utilizziamo prodotto.prezzo = 45;, TypeScript segnalerà un errore.

Pick<T, K>

Pick<T, K> permette di selezionare soltanto alcune proprietà di un tipo.

Supponiamo di avere:

interface Utente {
    id: number;
    nome: string;
    email: string;
    eta: number;
}

Se abbiamo bisogno di un tipo contenente soltanto nome ed email, possiamo utilizzare questa sintassi (con l'unione dei tipi):

type DatiContatto = Pick<Utente, "nome" | "email">;

Il risultato equivale a:

type DatiContatto = {
    nome: string;
    email: string;
};

La sintassi generale è Pick<T, K>, dove:

  • T è il tipo di partenza.
  • K rappresenta le proprietà da selezionare.

Le proprietà indicate da K devono essere effettivamente presenti in T.

Omit<T, K>

Omit<T, K> esegue l'operazione opposta rispetto a Pick<T, K>: invece di selezionare le proprietà da mantenere, permette di rimuovere determinate proprietà da un tipo.

Consideriamo nuovamente:

interface Utente {
    id: number;
    nome: string;
    email: string;
    eta: number;
}

Possiamo creare un tipo che contiene tutte le proprietà tranne id:

type NuovoUtente = Omit<Utente, "id">;

Il risultato equivale a:

type NuovoUtente = {
    nome: string;
    email: string;
    eta: number;
};

Possiamo quindi utilizzarlo quando vogliamo creare un nuovo utente senza specificare l'ID:

const nuovoUtente: NuovoUtente = {
    nome: "Andrea",
    email: "[email protected]",
    eta: 30
};

Omit con più proprietà

Possiamo rimuovere contemporaneamente più proprietà utilizzando un'unione:

type DatiPubblici = Omit<Utente, "email" | "eta">;

Il risultato sarà:

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

Combinare gli Utility Types

Gli Utility Types possono essere combinati tra loro.

Per esempio, possiamo creare un tipo che rappresenta i dati modificabili di un utente, rendendo opzionali tutte le proprietà tranne l'ID:

type ModificaUtente = Partial<Omit<Utente, "id">>;

Partendo dalla seguente interfaccia:

interface Utente {
    id: number;
    nome: string;
    email: string;
    eta: number;
}

Otteniamo un tipo equivalente a:

type ModificaUtente = {
    nome?: string;
    email?: string;
    eta?: number;
};

Possiamo quindi utilizzarlo per un aggiornamento parziale:

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

Questa possibilità di combinare diversi Utility Types permette di creare tipi complessi partendo da strutture già esistenti.

Prova!Completa gli spazi vuoti con il testo appropriato.
// Rendi obbligatorie tutte le proprietà
interface Corso {
    titolo: string;
    durata?: number;
}

const corso: <Corso> = {
    titolo: "TypeScript",
    durata: 10
};

Prova! Scegli l'opzione corretta tra quelle elencate.
// Rendi opzionali tutte le proprietà rimanenti
interface Articolo {
    id: number;
    titolo: string;
    contenuto: string;
}

type BozzaArticolo = ______<Omit<Articolo, "id">>;