Il type system di TypeScript è di tipo strutturale (Structural Type System). Questo vuol dire che il controllo e la comparazione dei tipi di dato da parte del compilatore si basa sulla struttura dei dati, sulla loro forma. Per comprendere meglio questo meccanismo facciamo un semplice esempio:
function inviaMessaggio(msg: {email: string, messaggio: string}) {
mailSender.sendTo(msg.email, msg.messaggio);
}
Supponendo l'esistenza di un oggetto mailSender
per inviare effettivamente una e-mail, la dichiarazione del parametro msg
della funzione inviaMessaggio()
indica che viene accettato un oggetto che abbia almeno le proprietà email
e messaggio
entrambi di tipo stringa.
Ciò significa che la seguente chiamata è corretta:
var mioMessaggio = {email: "mario.rossi@html.it", messaggio: "Buongiorno!"};
inviaMessaggio(mioMessaggio);
mentre quella che segue genera un errore a tempo di compilazione, dal momento che l'oggetto passato non contiene la proprietà email
:
var mioMessaggio = {messaggio: "Buongiorno!"};
inviaMessaggio(mioMessaggio);
Come possiamo vedere da questo semplice esempio, una volta dichiarata la struttura prevista per il parametro della funzione, TypeScript verifica che effettivamente venga passato un oggetto con quella struttura.
Per semplificare le cose, TypeScript consente la definizione di una interfaccia per definire la struttura del nostro parametro, come mostrato dal seguente esempio:
interface Messaggio {
email: string;
messaggio: string;
}
function inviaMessaggio(msg: Messaggio) {
mailSender.sendTo(msg.email, msg.messaggio);
}
Il costrutto interface
consente, in pratica, di definire un tipo di dato, Messaggio
nel nostro caso, la cui struttura viene verificata a tempo di compilazione da TypeScript. In questo modo, quindi, possiamo utilizzare il nome Messaggio
come se fosse un tipo di dato predefinito, permettendoci tra l'altro di riutilizzarlo in più punti della nostra applicazione.
La struttura dichiarata per un'interfaccia rappresenta gli elementi minimi che un oggetto deve avere. Cioè, ad esempio, la seguente chiamata sarà considerata da TypeScript sempre valida:
var mioMessaggio = {
email: "mario.rossi@html.it",
messaggio: "Buongiorno!",
data: new Date()
};
inviaMessaggio(mioMessaggio);
Il fatto che la proprietà data
non sia prevista dalla definizione dell'interfaccia Messaggio
non pregiudica la compatibilità dell'oggetto mioMessaggio
con il tipo previsto dal parametro della funzione inviaMessaggio()
.
La flessibilità delle interfacce di TypeScript si evidenzia nella possibilità di definire una struttura con elementi opzionali, come mostrato dal seguente esempio:
interface Messaggio {
email: string;
nomeDestinatario?: string;
oggetto?: string;
messaggio: string;
}
In questa interfaccia abbiamo aggiunto due proprietà il cui nome termina con un punto interrogativo: nomeDestinatario
e oggetto
. La presenza del punto interrogativo indica che le due proprietà sono opzionali, quindi TypeScript le accetta se sono presenti, ma non segnala un errore se sono assenti. La seguente chiamata, quindi, sarà considerata corretta da TypeScript:
var mioMessaggio = {
email: "mario.rossi@html.it",
oggetto: "Un saluto",
messaggio: "Buongiorno!"
};
inviaMessaggio(mioMessaggio);
Le interfacce di TypeScript non descrivono soltanto oggetti, come abbiamo visto negli esempi precedenti. Esse sono in grado di descrivere anche altri elementi del linguaggio. Ad esempio, il seguente codice mostra la definizione di una interfaccia che descrive una generica funzione su valori numerici:
interface FunzioneSuNumeri {
(x: number, y: number):number
}
Questa dichiarazione rappresenta funzioni che prendono due valori numerici come parametri e restituiscono un valore numerico. Quello che segue è un esempio di funzione del tipo appena dichiarato:
var somma: FunzioneSuNumeri;
somma = function(a:number, b:number) {
return a + b;
};
Occorre notare come la dichiarazione del tipo FunzioneSuNumeri
non ci obbliga ad utilizzare gli stessi nomi dei parametri x
e y
. Quello che è importante è che i tipi dei parametri siano corrispondenti.
Possiamo anche dichiarare interfacce che descrivono array, come nel seguente caso:
interface ArrayDiStringhe {
[index:number]: string
}
var x: ArrayDiStringhe = ["uno", "due", "tre"];
Come possiamo vedere, nella definizione di interfaccia abbiamo specificato il tipo dell'indice dell'array ed il tipo di ciascun elemento. Per l'indice dell'array, oltre al tipo number
è ammessa la possibilità di specificare il tipo string
, creando di fatto un dizionario:
interface Dizionario {
[index:string]: string
}
var x: Dizionario;
x["chiave1"] = "valore1";
x["chiave2"] = "valore2";
Non è prevista la possibilità di specificare tipi di dato diversi da number
e string
per l'indice di un array. Per gli elementi di un array, invece, possiamo specificare qualsiasi tipo. Il seguente è un esempio di interfaccia che definisce un array con indice numerico ed elementi costituiti da oggetti con una struttura predefinita:
interface ArrayDiOggetti {
[index: number]: {id: number, label: string};
}
var x: ArrayDiOggetti = [
{id:1, label:"aaa"},
{id:5, label:"bbb"}
];
DefinitelyTyped
La possibilità di definire interfacce ci consente di imporre dei vincoli al flusso di dati tra i componenti di un'applicazione TypeScript e di avere un maggior controllo già in fase di compilazione.
Questa possibilità può essere estesa anche a librerie JavaScript esistenti grazie a DefinitelyTyped, un repository Open Source che contiene le definizione di interfacce TypeScript per oltre 900 progetti JavaScript. Grazie ad esso è possibile includere in un nostro progetto TypeScript una libreria JavaScript esistente mantenendo il vantaggio del controllo dei tipi a tempo di compilazione. Tra le più note librerie e framework JavaScript per cui sono stati definiti i tipi segnaliamo jQuery, Bootstrap, AngularJS, KnockoutJS, Alertify.
Per semplificare l'utilizzo delle interfacce presenti sul repository, la community di DefinivelyTyped ha anche sviluppato TSD, un package manager che cerca e installa i file di definizione dall'interno dell'ambiente node.js
.
Avremo modo di ritornare sulle interfacce quando parleremo delle classi e del supporto di TypeScript alla programmazione ad oggetti.
Link utili