Gli oggetti in TypeScript

In TypeScript possiamo specificare con precisione la struttura degli oggetti, indicando quali proprietà possono contenere e quale tipo di valore deve avere ciascuna di esse.

Questa possibilità permette a TypeScript di controllare gli oggetti durante la compilazione e di fornire un autocompletamento più preciso nell'editor.

Possiamo definire direttamente la struttura dell'oggetto attraverso una dichiarazione esplicita del tipo, senza dover creare un tipo separato.

Dichiarare la struttura di un oggetto

In TypeScript, possiamo definire un oggetto con la seguente sintassi di base:

{
    proprietà1: tipo1;
    proprietà2: tipo2;
}

Ad esempio, possiamo definire un oggetto persona:

const persona: {
    nome: string;
    eta: number;
} = {
    nome: "Andrea",
    eta: 30
};

Abbiamo quindi specificato che persona deve essere un oggetto con:

  • Una proprietà nome di tipo string.
  • Una proprietà eta di tipo number.

Se proviamo ad assegnare un valore di tipo diverso, TypeScript segnala un errore perché eta deve essere un number:

const persona: {
    nome: string;
    eta: number;
} = {
    nome: "Andrea",
    eta: "30"
};

Proprietà obbligatorie

Quando definiamo una proprietà senza utilizzare altri modificatori, questa è obbligatoria.

Per esempio:

const persona: {
    nome: string;
    eta: number;
} = {
    nome: "Andrea"
};

Il codice genera un errore perché manca la proprietà eta.

Allo stesso modo, non possiamo aggiungere arbitrariamente proprietà che non fanno parte della struttura dichiarata:

const persona: {
    nome: string;
    eta: number;
} = {
    nome: "Andrea",
    eta: 30,
    email: "[email protected]"
};

TypeScript segnala un errore perché email non è prevista nella struttura dell'oggetto. Questo controllo permette di individuare facilmente proprietà mancanti o scritte in modo errato.

Proprietà opzionali

Non tutte le proprietà devono necessariamente essere presenti. Possiamo rendere una proprietà opzionale utilizzando ? dopo il suo nome, come per le tuple.

Ad esempio:

const persona: {
    nome: string;
    eta?: number;
} = {
    nome: "Andrea"
};

In questo caso nome è obbligatoria, mentre eta può essere omessa. 

Se la proprietà viene specificata, deve comunque rispettare il tipo indicato:

const persona: {
    nome: string;
    eta?: number;
} = {
    nome: "Andrea",
    eta: "30"
};

In questo caso, TypeScript genera un errore perché eta, quando presente, deve essere un number.

Leggere le proprietà degli oggetti

Per leggere il valore di una proprietà di un oggetto, possiamo usare la sintassi oggetto.proprietà.

Ad esempio:

const persona: {
    nome: string;
    eta: number;
} = {
    nome: "Andrea",
    eta: 30
};

console.log(persona.nome);

Quando una proprietà è opzionale, TypeScript considera anche la possibilità che il suo valore non sia presente. Per esempio:

const persona: {
    nome: string;
    eta?: number;
} = {
    nome: "Marco"
};

console.log(persona.eta);

Il valore di persona.eta può essere un number oppure undefined.

Oggetti immutabili

Possiamo impedire che una proprietà venga modificata dopo la creazione dell'oggetto utilizzando il modificatore readonly.

Per esempio:

const persona: {
    readonly id: number;
    nome: string;
} = {
    id: 1,
    nome: "Andrea"
};

Possiamo modificare nome, ma non id:

persona.nome = "Luca";
persona.id = 2; // Errore

Proprietà dinamiche

Finora abbiamo definito in anticipo tutte le proprietà che un oggetto può contenere.

In alcuni casi, però, abbiamo bisogno di gestire oggetti con proprietà dinamiche, cioè proprietà in cui i valori non si conoscono in anticipo.

Per esempio, potremmo avere un oggetto che contiene una serie di punteggi:

const punteggi = {
    Andrea: 10,
    Luca: 8,
    Anna: 9
};

Se vogliamo permettere un numero arbitrario di proprietà, possiamo utilizzare una firma dell'indice (in inglese, index signature).

La sintassi è:

{
    [key: string]: tipo;
}

Per esempio:

const punteggi: {
    [key: string]: number;
} = {
    Andrea: 10,
    Luca: 8,
    Anna: 9
};

In questo caso [key: string] indica che le proprietà possono avere nomi di tipo string, mentre il valore associato deve essere un number.

Utilizzare una firma dell'indice

Con le firme dell'indice, possiamo aggiungere nuove proprietà dinamicamente:

const punteggi: {
    [key: string]: number;
} = {};

punteggi["Paolo"] = 7;
punteggi["Sara"] = 10;

Il valore inserito dinamicamente deve comunque rispettare il tipo indicato:

const punteggi: {
    [key: string]: number;
} = {};

punteggi["Andrea"] = "10"; // Errore

Il codice nell'esempio genera un errore perché la firma dell'indice stabilisce che tutti i valori devono essere number.

Possiamo anche accedere alle proprietà utilizzando la notazione con parentesi:

console.log(punteggi["Andrea"]);

Firme dell'indice con proprietà definite

Una firma dell'indice può essere utilizzata anche insieme a proprietà specifiche.

Ad esempio:

const utente: {
    nome: string;
    [key: string]: string;
} = {
    nome: "Andrea",
    città: "Milano",
    paese: "Italia"
};

In questo caso nome è una proprietà definita esplicitamente, mentre le altre proprietà possono essere dinamiche.

È però necessario che il tipo della proprietà definita sia compatibile con quello della firma. Per esempio, questa struttura non è valida:

const utente: {
    nome: string;
    [key: string]: number;
} = {
    nome: "Andrea" // Errore
};

Nell'esempio, nome è infatti una string, mentre la firma dell'indice stabilisce che i valori devono essere number.

Prova!Completa gli spazi vuoti con il testo appropriato.
// Completa con i tipi corretti
const contatti: {
    [key: ]: ;
} = {};

contatti["Andrea"] = "[email protected]";

Prova! Scegli l'opzione corretta tra quelle elencate.
const libro: {
    readonly isbn: string;
    titolo: string;
} = {};

libro._____ = "Stringa";