Le interfacce in TypeScript

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.

Sintassi di un'interfaccia

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.

Proprietà obbligatorie

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.

Proprietà opzionali

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.

Proprietà readonly

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; // Errore

Tipizzare i metodi

Un'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;
    }
};

Metodi con parametri opzionali

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");

Interfacce per funzioni

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:

  • Riceve due number.
  • Restituisce un 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));

Funzioni invocabili con proprietà

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à.

Utilizzare le interfacce nelle funzioni

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);

Prova!Completa gli spazi vuoti con il testo appropriato.
// Definisci un'interfaccia in TypeScript
Veicolo {
    targa: string;
    velocita: number;
}

const auto: Veicolo = {
    targa: "AB123CD",
    velocita: 120
};

Prova! Scegli l'opzione corretta tra quelle elencate.
interface Documento {
    codice: string;
    _________: string;
}
const doc: Documento = {
    codice: "D1"
};