Interface è una struttura che definisce il contratto nella tua applicazione. Definisce la sintassi che le classi devono seguire. Le classi che derivano da un’interfaccia devono seguire la struttura fornita dalla loro interfaccia.
Il compilatore TypeScript non converte l’interfaccia in JavaScript. Usa l’interfaccia per il controllo dei tipi. Questo è anche conosciuto come “duck typing” o “structural subtyping”.
Un’interfaccia è definita con la parola chiave interface
e può includere proprietà e dichiarazioni di metodi usando una funzione o una funzione freccia.
Copia
interface IEmployee { empCode: number; empName: string; getSalary: (number) => number; // arrow function getManagerName(number): string; }
Nell’esempio precedente, l’interfaccia IEmployee
include due proprietà empCode
e empName
. Include anche una dichiarazione di metodo getSalaray
che usa una funzione freccia che include un parametro numero e un tipo di ritorno numero. Il metodo getManagerName
è dichiarato usando una funzione normale. Questo significa che qualsiasi oggetto di tipo IEmployee
deve definire le due proprietà e i due metodi.
Interfaccia come tipo
L’interfaccia in TypeScript può essere usata per definire un tipo e anche per implementarlo nella classe.
La seguente interfaccia IEmployee
definisce il tipo di una variabile.
Copia
interface KeyPair { key: number; value: string;}let kv1: KeyPair = { key:1, value:"Steve" }; // OKlet kv2: KeyPair = { key:1, val:"Steve" }; // Compiler Error: 'val' doesn't exist in type 'KeyPair'let kv3: KeyPair = { key:1, value:100 }; // Compiler Error:
Nell’esempio precedente, un’interfaccia KeyPair
include due proprietà key
e value
. Una variabile kv1
è dichiarata come il tipo KeyPair
. Quindi, deve seguire la stessa struttura di KeyPair
. Significa che solo un oggetto con proprietà key
di tipo numero e value
di tipo stringa può essere assegnato a una variabile kv1
. Il compilatore TypeScript mostrerà un errore se c’è qualche cambiamento nel nome delle proprietà o il tipo di dati è diverso da KeyPair
. Un’altra variabile kv2
è anche dichiarata come tipo KeyPair
ma il valore assegnato è val
invece di value
, quindi questo causerà un errore. Allo stesso modo, kv3 assegna un numero alla proprietà value
, quindi il compilatore mostrerà un errore. Così, TypeScript usa un’interfaccia per assicurare la corretta struttura di un oggetto.
Interfaccia come tipo di funzione
L’interfaccia di TypeScript è anche usata per definire il tipo di una funzione. Questo assicura la firma della funzione.
Copia
interface KeyValueProcessor{ (key: number, value: string): void;};function addKeyValue(key:number, value:string):void { console.log('addKeyValue: key = ' + key + ', value = ' + value)}function updateKeyValue(key: number, value:string):void { console.log('updateKeyValue: key = '+ key + ', value = ' + value)} let kvp: KeyValueProcessor = addKeyValue;kvp(1, 'Bill'); //Output: addKeyValue: key = 1, value = Bill kvp = updateKeyValue;kvp(2, 'Steve'); //Output: updateKeyValue: key = 2, value = Steve
Nell’esempio precedente, un’interfaccia KeyValueProcessor
include una firma di metodo. Questa definisce il tipo di funzione. Ora, possiamo definire una variabile di tipo KeyValueProcessor
che può puntare solo a funzioni con la stessa firma definita nell’interfaccia KeyValueProcessor
. Così, la funzione addKeyValue
o updateKeyValue
è assegnata a kvp
. Quindi, kvp
può essere chiamato come una funzione.
Tentare di assegnare una funzione con una firma diversa causerà un errore.
function delete(key:number):void { console.log('Key deleted.')} let kvp: KeyValueProcessor = delete; //Compiler Error
Interfaccia per il tipo di matrice
Un’interfaccia può anche definire il tipo di una matrice dove è possibile definire il tipo di indice così come i valori: Tipo di matrice Copia
interface NumList { :number}let numArr: NumList = ;numArr;numArr;interface IStringList { :string}let strArr : IStringList;strArr = "TypeScript";strArr = "JavaScript";
Nell’esempio precedente, l’interfaccia NumList
definisce un tipo di matrice con indice come numero e valore come tipo numero. Allo stesso modo, IStringList
definisce un array di stringhe con indice come stringa e valore come stringa.
Proprietà opzionali
A volte, possiamo dichiarare un’interfaccia con un eccesso di proprietà ma non ci aspettiamo che tutti gli oggetti definiscano tutte le proprietà dell’interfaccia data. Possiamo avere proprietà opzionali, marcate con un “?”. In questi casi, gli oggetti dell’interfaccia possono definire o meno queste proprietà.
Copy
interface IEmployee { empCode: number; empName: string; empDept?:string;}let empObj1:IEmployee = { // OK empCode:1, empName:"Steve"}let empObj2:IEmployee = { // OK empCode:1, empName:"Bill", empDept:"IT"}
Nell’esempio precedente, empDept
è marcato con ?
, quindi gli oggetti di IEmployee
possono includere o meno questa proprietà.
Proprietà in sola lettura
TypeScript fornisce un modo per marcare una proprietà come in sola lettura. Questo significa che una volta che ad una proprietà è stato assegnato un valore, non può essere cambiato!
Copy
interface Citizen { name: string; readonly SSN: number;}let personObj: Citizen = { SSN: 110555444, name: 'James Bond' }personObj.name = 'Steve Smith'; // OKpersonObj.SSN = '333666888'; // Compiler Error
Nell’esempio precedente, la proprietà SSN
è di sola lettura. Definiamo l’oggetto personObj di tipo Citizen e assegniamo valori alle due proprietà dell’interfaccia. Successivamente, proviamo a cambiare i valori assegnati ad entrambe le proprietàname
e SSN
. Il compilatore TypeScript mostra un errore quando cerchiamo di cambiare la proprietà SSN
di sola lettura.
Extendere le interfacce
Le interfacce possono estendere una o più interfacce. Questo rende la scrittura delle interfacce flessibile e riutilizzabile.
Copia
interface IPerson { name: string; gender: string;}interface IEmployee extends IPerson { empCode: number;}let empObj:IEmployee = { empCode:1, name:"Bill", gender:"Male"}
Nell’esempio precedente, l’interfaccia IEmployee
estende l’interfaccia IPerson
. Quindi, gli oggetti di IEmployee
devono includere tutte le proprietà e i metodi dell’interfaccia IPerson
altrimenti il compilatore mostrerà un errore.
Implementare un’interfaccia
Similmente a linguaggi come Java e C#, le interfacce in TypeScript possono essere implementate con una classe. La classe che implementa l’interfaccia deve essere strettamente conforme alla struttura dell’interfaccia.
Copia
interface IEmployee { empCode: number; name: string; getSalary:(number)=>number;}class Employee implements IEmployee { empCode: number; name: string; constructor(code: number, name: string) { this.empCode = code; this.name = name; } getSalary(empCode:number):number { return 20000; }}let emp = new Employee(1, "Steve");
Nell’esempio precedente, l’interfaccia IEmployee
è implementata nella classe Employee usando la parola chiave implementazione. La classe implementatrice dovrebbe rigorosamente definire le proprietà e la funzione con lo stesso nome e tipo di dati. Se la classe di implementazione non segue la struttura, allora il compilatore mostrerà un errore.
Certo, la classe implementatrice può definire proprietà e metodi extra, ma almeno deve definire tutti i membri di un’interfaccia.
Nel prossimo capitolo, impareremo di più sulle classi TypeScript.