Quando lavoriamo con oggetti complessi, può essere utile definire in modo preciso quali proprietà e quali metodi devono avere.
TypeScript permette di farlo attraverso le interfacce, definite con la keyword interface. Un'interfaccia descrive la forma di un oggetto, specificando quali proprietà può o deve contenere e quali tipi devono avere.
Per esempio, possiamo utilizzare un'interfaccia Persona per tipizzare un oggetto persona: in questo modo, se manca una proprietà obbligatoria, TypeScript segnalerà un errore. Vediamo in dettaglio come funzionano le interfacce.
La sintassi base delle interfacce è simile a quella degli oggetti:
interface NomeInterfaccia {
proprieta: tipo;
}Per esempio, possiamo creare l'interfaccia Prodotto:
interface Prodotto {
nome: string;
prezzo: number;
}Un oggetto compatibile con questa interfaccia deve avere entrambe le proprietà:
const prodotto: Prodotto = {
nome: "Mouse",
prezzo: 29.99
};Le proprietà possono avere qualsiasi tipo supportato da TypeScript: ad esempio string, number o boolean.
Le proprietà dichiarate normalmente all'interno di un'interfaccia sono obbligatorie.
Ad esempio:
interface Persona {
nome: string;
cognome: string;
eta: number;
}Un oggetto deve avere tutte le proprietà obbligatorie dell'interfaccia:
const persona1: Persona = {
nome: "Andrea",
cognome: "Rossi",
eta: 30
};
// Errore
const persona2: Persona = {
nome: "Marco",
cognome: "Rossi"
};La proprietà eta è obbligatoria e deve quindi essere presente.
Possiamo rendere una proprietà opzionale utilizzando ?:
interface Persona {
nome: string;
eta?: number;
}Ora eta può essere omessa. Entrambi gli oggetti sono validi:
const persona1: Persona = {
nome: "Andrea"
};
const persona2: Persona = {
nome: "Marco",
eta: 30
};Una proprietà opzionale può quindi essere presente oppure assente. Questo è particolarmente utile quando un oggetto può avere alcune informazioni non sempre disponibili.
Come anche per le tuple e le altre variabili in TypeScript, possiamo utilizzare readonly per indicare che una proprietà non deve essere modificata dopo la creazione dell'oggetto.
Ad esempio:
interface Utente {
readonly id: number;
nome: string;
}Possiamo creare l'oggetto normalmente:
const utente: Utente = {
id: 1,
nome: "Andrea"
};Possiamo modificare il nome, ma non l'id:
utente.nome = "Luca";
utente.id = 2; // ErroreUn'interfaccia può descrivere non soltanto proprietà, ma anche metodi che un oggetto deve avere.
Ad esempio:
interface Persona {
nome: string;
saluta(): void;
}Qualsiasi oggetto tipizzato come Persona dovrà quindi avere un metodo saluta che non restituisce un valore:
const persona: Persona = {
nome: "Andrea",
saluta() {
console.log("Ciao!");
}
};Possiamo anche specificare parametri e tipo di ritorno:
interface Calcolatrice {
somma(a: number, b: number): number;
}L'oggetto dovrà implementare un metodo compatibile:
const calcolatrice1: Calcolatrice = {
somma(a, b) {
return a + b;
}
};Anche i parametri dei metodi possono essere opzionali. Prendiamo questo esempio:
interface Messaggio {
invia(testo: string, destinatario?: string): void;
}Possiamo quindi implementare:
const messaggio: Messaggio = {
invia(testo, destinatario) {
console.log(testo);
}
};Il metodo può essere chiamato con uno o due argomenti:
messaggio.invia("Ciao");
messaggio.invia("Ciao", "Andrea");Un'interfaccia può anche descrivere una funzione invocabile, cioè specificare quali parametri deve accettare e quale valore deve restituire.
Utilizziamo un esempio per capire la sintassi base:
interface Operazione {
(a: number, b: number): number;
}Questa interfaccia indica che Operazione rappresenta una funzione che:
number.number.Possiamo quindi assegnare una funzione compatibile:
const somma: Operazione = (a, b) => {
return a + b;
};Ora possiamo utilizzare la funzione normalmente:
console.log(somma(10, 5));Alle interfacce per le funzioni, possiamo comunque aggiungere delle proprietà:
interface Funzione {
(valore: number): number;
descrizione: string;
}Questa interfaccia descrive un valore che può essere invocato come una funzione e che possiede anche una proprietà descrizione.
Possiamo creare un valore compatibile:
const doppio: Funzione = (valore) => {
return valore * 2;
};
doppio.descrizione = "Restituisce il doppio di un numero";Possiamo quindi utilizzare entrambe le caratteristiche:
console.log(doppio(10));
console.log(doppio.descrizione);Questo tipo di struttura è possibile perché in JavaScript le funzioni sono oggetti e possono quindi avere anche proprietà.
Le interfacce sono particolarmente utili come tipo dei parametri delle funzioni.
Per esempio:
interface Utente {
nome: string;
email: string;
}Possiamo utilizzare l'interfaccia nella funzione:
function mostraUtente(utente: Utente): void {
console.log(utente.nome);
console.log(utente.email);
}Ora TypeScript sa esattamente quali proprietà sono disponibili all'interno della funzione:
const utente: Utente = {
nome: "Andrea",
email: "[email protected]"
};
mostraUtente(utente);