Quando un progetto TypeScript cresce, può essere utile combinare tipi già esistenti invece di definirli nuovamente da zero.
TypeScript offre due strumenti principali per farlo:
extends, utilizzato principalmente per estendere le interfacce.&, utilizzato per creare un'intersezione di tipi, cioè un tipo che deve rispettare contemporaneamente più tipi (non preoccuparti, ne parliamo in dettaglio più in basso!).Entrambi permettono di costruire strutture più complesse partendo da definizioni già esistenti.
Un'interfaccia può estendere un'altra interfaccia utilizzando extends.
Ad esempio, definiamo un'interfaccia iniziale chiamata Persona:
interface Persona {
nome: string;
eta: number;
}Possiamo creare un'interfaccia Dipendente che eredita tutte le proprietà di Persona (visto che un dipendente ha anche le caratteristiche di una persona, come nome e eta):
interface Dipendente extends Persona {
stipendio: number;
}Ora, un Dipendente dovrà quindi avere: nome, eta e stipendio.
Ad esempio:
const dipendente: Dipendente = {
nome: "Andrea",
eta: 30,
stipendio: 2500
};L'interfaccia che estende un'altra interfaccia può aggiungere nuove proprietà e metodi.
Ad esempio:
interface Persona {
nome: string;
}
interface Studente extends Persona {
corso: string;
}Ora Studente contiene entrambe le proprietà:
const studente: Studente = {
nome: "Andrea",
corso: "TypeScript"
};La proprietà nome proviene da Persona, mentre corso viene aggiunta da Studente.
Un'interfaccia può estendere più interfacce contemporaneamente.
È sufficiente separare i nomi con una virgola:
interface Persona {
nome: string;
}
interface Dipendente {
stipendio: number;
}
interface Manager extends Persona, Dipendente {
reparto: string;
}In questo esempio, Manager è sia un dipendente dell'azienda, che una persona. Manager eredita quindi le proprietà di entrambe le interfacce:
const manager: Manager = {
nome: "Andrea",
stipendio: 3500,
reparto: "IT"
};In questo modo possiamo comporre un'interfaccia a partire da più strutture già definite.
Un'interfaccia può estendere un'altra interfaccia che a sua volta ne estende un'altra.
Ad esempio:
interface Persona {
nome: string;
}
interface Dipendente extends Persona {
stipendio: number;
}
interface Manager extends Dipendente {
reparto: string;
}Nell'esempio, Dipendente estende Persona (perché un dipendente è anche una persona) e Manager estende Dipendente (perché il manager è anche un dipendente dell'azienda). Manager eredita quindi tutte le proprietà della catena:
const manager: Manager = {
nome: "Andrea",
stipendio: 3500,
reparto: "IT"
};Quando si utilizza extends, TypeScript non consente di ridefinire una proprietà ereditata con un tipo diverso da quello originale.
Ad esempio:
interface Persona {
id: string;
}
// Errore
interface Dipendente extends Persona {
id: number;
}In questo caso, la proprietà id è definita come string in Persona, ma viene ridefinita come number in Dipendente. Poiché una stessa proprietà non può avere due tipi incompatibili contemporaneamente, TypeScript segnala un errore.
Questo aiuta a evitare strutture incoerenti durante l'estensione delle interfacce.
Le interfacce non sono l'unico modo per combinare tipi. TypeScript permette di creare un'intersezione di tipi utilizzando l'operatore &.
Ad esempio, abbiamo i tipi Persona e Dipendente:
type Persona = {
nome: string;
};
type Dipendente = {
stipendio: number;
};Possiamo creare un tipo che combina entrambi:
type Manager = Persona & Dipendente;Un valore di tipo Manager deve rispettare entrambi i tipi:
const manager: Manager = {
nome: "Andrea",
stipendio: 3500
};È importante distinguere l'operatore di intersezione & dall'operatore di unione |.
Con | definiamo un'alternativa:
type Persona = {
nome: string;
};
type Azienda = {
ragioneSociale: string;
};
type Soggetto = Persona | Azienda;Un valore Soggetto può essere un Persona oppure un Azienda.
Con &, invece, il valore deve rispettare entrambi i tipi:
type Soggetto = Persona & Azienda;L'operatore & può essere utilizzato anche con le interfacce. Ad esempio:
interface Persona {
nome: string;
}
interface Dipendente {
stipendio: number;
}
type Manager = Persona & Dipendente;Il risultato è equivalente, dal punto di vista della struttura, a un tipo che richiede entrambe le proprietà.
Bisogna prestare attenzione quando intersechiamo due tipi che possiedono una proprietà con lo stesso nome ma tipi differenti.
Per esempio:
type Persona = {
id: string;
};
type Prodotto = {
id: number;
};Proviamo ora ad intersecare Persona e Prodotto:
type Elemento = Persona & Prodotto;Elemento dovrebbe ora avere contemporaneamente id: string e id: number. Ma un singolo valore non può essere contemporaneamente una string e un number.
Di conseguenza, la proprietà id diventa di fatto impossibile da soddisfare.