Перейти к содержимому
Теория · Интерфейсы и структурная типизация · урок 1 из 12

Интерфейсы

Чему вы научитесь

Чему вы научитесь:

  • описывать форму объекта через interface и собирать новые формы через extends и &;
  • объяснять, почему TypeScript сравнивает типы по форме, а не по имени, и когда литерал с лишним свойством не проходит;
  • описывать словарь индексной сигнатурой и помнить, что несуществующий ключ tsc не заметит;
  • выбирать между type и interface.
Замок Point с выемками x и y открывает любой ключ с такими же зубцами, а ключ с другими зубцами не подходит

Представьте навесной замок. Внутри у него выемки, и он откроется, если зубцы ключа встанут ровно напротив них. Замку всё равно, какой фирмы ключ и что выбито на его головке: он проверяет только зубцы.

Объекты в TypeScript проверяются похоже. Функции, которая считает расстояние от точки до начала координат, неважно, откуда пришёл объект. Ей нужно одно: чтобы в нём были числа x и y. Раньше вы описывали такую форму через type. У TypeScript есть и второй способ, который часто встречается в чужом коде, — интерфейс: именованное описание формы объекта, его свойств и методов.

TypeScript
interface Point {
    x: number;
    y: number;
}

function distance(p: Point): number {
    return Math.sqrt(p.x * p.x + p.y * p.y);
}

console.log(distance({ x: 3, y: 4 })); // 5

Давайте разберём пример по строкам:

  1. interface Point { … } объявляет интерфейс Point: у объекта должны быть свойства x и y, оба числа. Это и есть замок с двумя выемками.
  2. Параметр p получает тип Point, поэтому внутри функции TypeScript знает, что p.x и p.y — числа.
  3. Вызов передаёт литерал { x: 3, y: 4 }. Он подходит под Point, и программа печатает 5.

Как устроено объявление

Объявление интерфейса состоит из трёх частей:

  • ключевое слово interface;
  • имя с большой буквы: Point, User, Order;
  • фигурные скобки со списком членов, где у каждого члена имя, двоеточие, тип и точка с запятой.

Сравните с псевдонимом типа. В записи type Point = { x: number; y: number }; после имени стоит знак =, а у интерфейса после имени сразу идут фигурные скобки. Проверяет компилятор оба способа одинаково: литерал без y в обоих случаях получит ошибку TS2741.

Интерфейс, как любой тип, существует только для компилятора. В JavaScript-файле, который запишет tsc, от него не останется ни строчки.

Что бывает внутри интерфейса

Посмотрим на интерфейс, где собраны все виды членов, которые встретятся в курсе чаще всего:

TypeScript
interface User {
    readonly id: number;
    name: string;
    email?: string;
    greet(): string;
}

const ann: User = {
    id: 1,
    name: "Ann",
    greet() {
        return `Hi, I am ${this.name}`;
    },
};
console.log(ann.greet(), ann.email); // Hi, I am Ann undefined

Обычное свойство

name: string — свойство обязательно, и в нём лежит строка. Объект без name или с числом в name компилятор не пропустит. Так описывают всё, что есть у каждого пользователя.

Необязательное свойство

email?: string — свойство можно не указывать. Как и в type, читается оно как string | undefined. У ann почты нет, поэтому на месте ann.email программа печатает undefined. Знак ? ставят у того, что есть не у всех.

Свойство только для чтения

readonly id: number — значение задают при создании объекта, а записать потом ann.id = 2 tsc не даст: ошибка TS2540. Так помечают номера и всё, что после создания не меняется.

Метод

greet(): string — у объекта должен быть метод greet, который не принимает аргументов и возвращает строку. В интерфейсе пишут только имя метода, скобки с параметрами и тип результата, а тело метода — в самом объекте. Объект без greet не подойдёт: tsc сообщит, что свойства greet не хватает.

Внутри метода this — сам объект, поэтому this.name даёт "Ann", и программа печатает Hi, I am Ann undefined.

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

Частая ошибка

Интерфейс записали как псевдоним типа, со знаком =:

TypeScript
interface Point = { x: number; y: number }; // ✗
TEXT
error TS1005: '{' expected.

Сообщение значит «ожидалась {»: после имени интерфейса компилятор ждёт фигурную скобку, а встречает =. Уберите знак =, и объявление станет верным: interface Point { x: number; y: number }.

Главное

  • interface Point { x: number; y: number } даёт имя форме объекта; знака = после имени нет.
  • Члены интерфейса — обычные свойства, необязательные (?), readonly-свойства и методы вроде greet(): string.
  • У метода в интерфейсе есть имя, параметры и тип результата, а тело пишут в объекте.
  • Интерфейс, как любой тип, видит только компилятор: в JavaScript его нет.