Интерфейсы

Последнее обновление: 17.05.2021

Интерфейсы объектов

Интерфейс определяет свойства и методы, которые объект должен реализовать. Другими словами, интерфейс - это определение кастомного типа данных, но без реализации. В данном случае интерфейсы в TS похожи на интерфейсы в языках Java и C#. Интерфейсы определяются с помощью ключевого слова interface. Для начала определим простенький интерфейс:

interface IUser {
    id: number;
    name: string;
}

Интерфейс в фигурных скобках определяет два свойства: id, которое имеет тип number, и name, которая представляет строку. Теперь используем его в программе:

let employee: IUser = {
    
    id: 1, 
    name: "Tom"
}
console.log("id: ", employee.id);
console.log("name: ", employee.name);

По сути employee - обычный объект за тем исключением, что он имеет тип IUser. Если правильнее говорить, то employee реализует интерфейс IUser. Причем эта реализация накладывает на employee некоторые ограничения. Так, employee должен реализовать все свойства и методы интерфейса IUser, поэтому при определении employee данный объект обязательно должен включать в себя свойства id и name.

Параметры методов и функций также могут представлять интерфейсы:

interface IUser {
    id: number;
    name: string;
}
let employee: IUser = {
    
    id: 1, 
    name: "Alice"
}

function printUser(user: IUser): void {

    console.log("id: ", user.id);
    console.log("name: ",  user.name)
}

printUser(employee);

В этом случае аргумент, который передается в функцию, должен представлять объект или класс, который реализует соответствующий интерфейс.

И также можно возвращать объекты интерфейса:

interface IUser {
    id: number;
    name: string;
}
function buildUser(userId: number, userName: string): IUser {

    return { id: userId, name: userName };
}

let newUser = buildUser(2, "Bill");
console.log("id: ", newUser.id);
console.log("name: ", newUser.name);

Необязательные свойства и свойства только для чтения

При определении интерфейса мы можем задать некоторые свойства как необязательные с помощью знака вопроса. Подобные свойства реализовать необязательно:

interface IUser {
    id: number;
    name: string;
    age?: number;
}
let employee: IUser = {
    
    id: 1, 
    name: "Alice",
    age: 23
}
let manager: IUser = {
    
    id: 2, 
    name: "Tom"
}

Свойство age помечено как необязательное, поэтому его можно не определять в объектах.

Также интерфейс может содержать свойства только для чтения, значение которых нельзя изменять. Такие свойства определяются с помощью ключевого слова readonly:

interface Point {
    readonly x: number;
    readonly y: number;
}
let p: Point = { x: 10, y: 20 };
console.log(p);
// p.x = 5; // Ошибка - свойство доступно только для чтения

Определение методов

Кроме свойств интерфейсы могут определять функции:

interface IUser {
    id: number;
    name: string;
    sayWords(words: string): void;
}
let employee: IUser = {
     
    id: 1, 
    name: "Alice",
    sayWords: function(words: string): void{
		console.log(`${this.name} говорит "${words}"`);
	}
}
 
employee.sayWords("Привет, как дела?");

Опять же объект, который реализует интерфейс, также обязан реализовать определенную в интерфейсе функцию с тем же набором параметров и тем типом выходного результата. В данном случае функция sayWords() в качестве параметра принимает строку и ничего возвращает, выводя на консоль некоторое сообщение.

Интерфейсы классов

Интерфейсы могут быть реализованы не только объектами, но и классами. Для этого используется ключевое слово implements:

interface IUser {
    id: number;
    name: string;
    getFullName(surname: string): string;
}

class User implements IUser{

    id: number;
    name: string;
    age: number;
    constructor(userId: number, userName: string, userAge: number) {

        this.id = userId;
        this.name = userName;
        this.age = userAge;
    }
    getFullName(surname: string): string {

        return this.name + " " + surname;
    }
}

let tom = new User(1, "Tom", 23);
console.log(tom.getFullName("Simpson"));

Класс User реализует интерфейс IUser. В этом случае класс User обязан определить все те же свойства и функции, которые есть в IUser.

При этом объект tom является как объектом User, так и объектом IUser:

let tom :IUser = new User(1, "Tom", 23);
//или
let tom :User = new User(1, "Tom", 23);

Расширение интерфейса

TypeScript позволяет добавлять в интерфейс новые поля и методы, просто объявив интерфейс с тем же именем и определив в нем необходимые поля и методы. Например:

interface IUser {
    id: number;
    name: string;
}
interface IUser{
	age: number;
}
let employee: IUser = {
    
    id: 1, 
    name: "Alice",
	age: 31
}

function printUser(user: IUser): void {

    console.log(`id: ${user.id}  name: ${user.name}  age: ${user.age}`);
}

printUser(employee);

В данном случае первое определение интерфейса IUser содержит поля id и name. Второе определение интерфейса содержит объявление поля age. В итоге объект или класс, который реализует этот интерфейс, должен определить все три поля - id, name и age.

Наследование интерфейсов

Интерфейсы, как и классы, могут наследоваться:

interface IMovable {

    speed: number;
    move(): void;
}
interface ICar extends IMovable {

    fill(): void;
}
class Car implements ICar {

    speed: number;
    move(): void {

        console.log("Машина едет со скоростью " + this.speed + " км/ч");
    }

    fill(): void {

        console.log("Заправляем машину топливом");
    }
}

let auto = new Car();
auto.speed = 60;
auto.fill();
auto.move();

После наследования интерфейс ICar будет также иметь все те свойства и функции, которые определены в IMovable. И тогда, класс Car, реализующий интерфейс ICar, должен будет реализовать также и свойства и методы интерфейса IMovable.

Интерфейсы функций

Интерфейсы функций содержат определение типа функции. Затем они должны быть реализованы объектом, который представляет функцию данного типа:

interface FullNameBuilder {
    (name: string, surname: string): string;
}

let simpleBuilder: FullNameBuilder = function (name:string, surname: string): string {
        return "Mr. " + name + " " + surname;
}

let fullName = simpleBuilder("Bob", "Simpson");
console.log(fullName); // Mr. Bob Simpson

Здесь определен интерфейс FullNameBuilder, который лишь содержит сигнатуру функции. Далее определяется переменная simpleBuilder, которая имеет тип FullNameBuilder и поэтому должна представлять функцию с данной сигнатурой.

Интерфейсы массивов

Интерфейсы массивов описывают объекты, к которым можно обращаться по индексу, как, например, к массивам

interface StringArray {
    [index: number]: string;
}

let phones: StringArray;
phones = ["iPhone 7", "HTC 10", "HP Elite x3"];

let myPhone: string = phones[0];
console.log(myPhone);

Здесь определен интерфейс StringArray, который содержит сигнатуру массива. Эта сигнатура указывает, что объект, который реализует StringArray, может индексироваться с помощью чисел (объекта типа number). И, кроме того, данный объект должен хранить объекты типа string, то есть строки.

Выше индекс представлял тип number. Но мы можем использовать для индексации и тип string:

interface Dictionary {
    [index: string]: string;
}

var colors: Dictionary = {};
colors["red"] = "#ff0000";
colors["green"] = "#00ff00";
colors["blue"] = "#0000ff";

console.log(colors["red"]);

Гибридные интерфейсы

Интерфейсы могут сочетать различные стили, могут применяться сразу как к определению объекта, так и к определению функции:

interface PersonInfo {
    (name: string, surname: string):void;
    fullName: string;
    password: string;
    authenticate(): void;
}

function personBuilder(): PersonInfo {

    let person = <PersonInfo>function (name: string, surname: string): void{
        person.fullName = name + " " + surname;
    };
    person.authenticate = function () {
        console.log(person.fullName + " входит в систему с паролем " + person.password);
    };
    return person;
}

let tom = personBuilder();
tom("Tom", "Simpson");
tom.password = "qwerty"; 
tom.authenticate();

Тип функции, определяемый в таком гибридном интерфейсе, как правило, выступает в роли конструктора объекта. В данном случае такой конструктор имеет тип (name: string, surname: string):void;.

А функция, которая представляет данный интерфейс (в данном случае - функция personBuilder), реализует эту функцию конструктора, и также может использовать другие свойства и методы, которые были определены в интерфейсе.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850