Interfejs jest strukturą, która definiuje kontrakt w twojej aplikacji. Definiuje składnię, której muszą przestrzegać klasy. Klasy, które wywodzą się z interfejsu muszą przestrzegać struktury dostarczonej przez ich interfejs.
Kompilator TypeScript nie konwertuje interfejsu na JavaScript. Używa on interfejsu do sprawdzania typu. Jest to również znane jako „duck typing” lub „structural subtyping”.
Interfejs jest zdefiniowany za pomocą słowa kluczowego interface
i może zawierać właściwości i deklaracje metod za pomocą funkcji lub funkcji strzałki.
Copy
interface IEmployee { empCode: number; empName: string; getSalary: (number) => number; // arrow function getManagerName(number): string; }
W powyższym przykładzie interfejs IEmployee
zawiera dwie właściwości empCode
i empName
. Zawiera on również deklarację metody getSalaray
wykorzystującej funkcję strzałki, która zawiera jeden parametr liczbowy i typ zwracany liczbowy. Metoda getManagerName
jest zadeklarowana przy użyciu normalnej funkcji. Oznacza to, że każdy obiekt typu IEmployee
musi zdefiniować te dwie właściwości i dwie metody.
Interfejs jako typ
Interfejs w TypeScript może być użyty do zdefiniowania typu, a także do zaimplementowania go w klasie.
Następujący interfejs IEmployee
definiuje typ zmiennej.
Copy
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:
W powyższym przykładzie interfejs KeyPair
zawiera dwie właściwości key
i value
. Zmienna kv1
jest zadeklarowana jako typ KeyPair
. Zatem musi ona mieć taką samą strukturę jak KeyPair
. Oznacza to, że do zmiennej kv1
może być przypisany tylko obiekt o właściwościach key
typu liczbowego i value
typu łańcuchowego. Kompilator TypeScript wyświetli błąd, jeżeli dojdzie do zmiany nazwy właściwości lub typ danych będzie inny niż KeyPair
. Inna zmienna kv2
jest również zadeklarowana jako typ KeyPair
, ale przypisana wartość to val
zamiast value
, więc spowoduje to błąd. W ten sam sposób, kv3 przypisuje liczbę do właściwości value
, więc kompilator pokaże błąd. Tak więc TypeScript używa interfejsu, aby zapewnić właściwą strukturę obiektu.
Interfejs jako typ funkcji
Interfejs TypeScript jest również używany do definiowania typu funkcji. Zapewnia to sygnaturę funkcji.
Kopia
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
W powyższym przykładzie interfejs KeyValueProcessor
zawiera sygnaturę metody. To definiuje typ funkcji. Teraz możemy zdefiniować zmienną typu KeyValueProcessor
, która może wskazywać tylko na funkcje z taką samą sygnaturą, jak zdefiniowana w interfejsie KeyValueProcessor
. Tak więc, addKeyValue
lub updateKeyValue
funkcja jest przypisana do kvp
. Tak więc, kvp
może być wywoływany jak funkcja.
Próba przypisania funkcji o innym podpisie spowoduje błąd.
function delete(key:number):void { console.log('Key deleted.')} let kvp: KeyValueProcessor = delete; //Compiler Error
Interfejs dla typu tablicy
Interfejs może również definiować typ tablicy, gdzie można zdefiniować typ indeksu, jak również wartości.
Copy
interface NumList { :number}let numArr: NumList = ;numArr;numArr;interface IStringList { :string}let strArr : IStringList;strArr = "TypeScript";strArr = "JavaScript";
W powyższym przykładzie interfejs NumList
definiuje typ tablicy z indeksem jako liczba i wartością jako typ liczbowy. W ten sam sposób, IStringList
definiuje tablicę typu string z indeksem jako string i wartością jako string.
Właściwości opcjonalne
Czasami możemy zadeklarować interfejs z nadmiarem właściwości, ale nie możemy oczekiwać, że wszystkie obiekty będą definiować wszystkie podane właściwości interfejsu. Możemy mieć właściwości opcjonalne, oznaczone znakiem „?”. W takich przypadkach obiekty interfejsu mogą, ale nie muszą definiować tych właściwości.
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"}
W powyższym przykładzie empDept
jest oznaczony symbolem ?
, więc obiekty IEmployee
mogą, ale nie muszą zawierać tej właściwości.
Właściwości tylko do odczytu
TypeScript zapewnia sposób oznaczania właściwości jako tylko do odczytu. Oznacza to, że gdy właściwości zostanie przypisana wartość, nie można jej zmienić!
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
W powyższym przykładzie właściwość SSN
jest tylko do odczytu. Definiujemy obiekt personObj typu Citizen i przypisujemy wartości dwóm właściwościom interfejsu. Następnie spróbujemy zmienić wartości przypisane obu właściwościom –name
i SSN
. Kompilator TypeScript wyświetli błąd, gdy spróbujemy zmienić właściwość SSN
tylko do odczytu.
Rozszerzanie interfejsów
Interfejsy mogą rozszerzać jeden lub więcej interfejsów. To sprawia, że pisanie interfejsów jest elastyczne i wielokrotnego użytku.
Copy
interface IPerson { name: string; gender: string;}interface IEmployee extends IPerson { empCode: number;}let empObj:IEmployee = { empCode:1, name:"Bill", gender:"Male"}
W powyższym przykładzie interfejs IEmployee
rozszerza interfejs IPerson
. Zatem obiekty IEmployee
muszą zawierać wszystkie właściwości i metody interfejsu IPerson
, w przeciwnym razie kompilator wyświetli błąd.
Implementacja interfejsu
Podobnie do języków takich jak Java i C#, interfejsy w TypeScript mogą być implementowane za pomocą klasy. Klasa implementująca interfejs musi być ściśle zgodna ze strukturą interfejsu.
Kopia
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");
W powyższym przykładzie interfejs IEmployee
jest implementowany w klasie Employee za pomocą słowa kluczowego the implement. Klasa implementująca powinna ściśle zdefiniować właściwości i funkcję o tej samej nazwie i typie danych. Jeśli klasa implementująca nie podąża za strukturą, wtedy kompilator pokaże błąd.
Oczywiście klasa implementująca może zdefiniować dodatkowe właściwości i metody, ale przynajmniej musi zdefiniować wszystkich członków interfejsu.
W następnym rozdziale dowiemy się więcej o klasach TypeScript.