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

type или interface

В мастерской описания замков хранят двумя способами. Одни подшивают в папку-скоросшиватель: в неё кладут только чертежи, зато новый лист можно подшить позже, и папка соберёт все листы вместе. Другие помечают этикеткой с названием. Этикетку можно наклеить на что угодно — на чертёж, на коробку «этот замок или тот», на пару предметов. Но вторую этикетку с тем же названием наклеить нельзя.

С interface и type то же самое: интерфейс — папка, псевдоним типа — этикетка. Оба описывают форму объекта, и чаще всего между ними нет разницы. Отличаются они в двух местах, и о них любят спрашивать на собеседовании: «Чем type отличается от interface?»

Для объектов одно и то же

Опишем одну форму обоими способами:

TypeScript
type PointType = {
    x: number;
    y: number;
};

interface PointShape {
    x: number;
    y: number;
}

const a: PointType = { x: 1, y: 2 };
const b: PointShape = a;
console.log(b.x + b.y); // 3

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

  1. PointType и PointShape описывают одну и ту же форму: числа x и y.
  2. a объявлена как PointType, а строка const b: PointShape = a; всё равно проходит. Типы сравниваются по форме, а формы совпали.
  3. Программа печатает 3.

Для формы объекта неважно, каким способом её записали:

  • tsc проверяет литерал одинаково: недостающее свойство — TS2741, лишнее — TS2353;
  • ? и readonly работают в обоих;
  • оба стираются и в JavaScript не попадают;
  • форму расширяют через extends у интерфейса и через & у псевдонима.

Только type: объединения и кортежи

Этикетку можно наклеить на что угодно, а папка хранит только чертежи. Псевдоним даёт имя любому типу:

TypeScript
type Id = string | number;
type Pair = [string, number];

const id: Id = 42;
const entry: Pair = ["apple", 5];
console.log(id, entry[0]); // 42 apple

Интерфейс же описывает только форму объекта. Записать через него объединение string | number или кортеж нельзя: запись interface Id = string | number; tsc даже не разберёт и ответит error TS1005: '{' expected.

Только interface: слияние объявлений

Два объявления интерфейса с одним именем не спорят, а складываются в одно:

TypeScript
interface Config {
    port: number;
}

interface Config {
    host: string;
}

const config: Config = { port: 8080, host: "localhost" };
console.log(`${config.host}:${config.port}`); // localhost:8080

Это слияние объявлений: у Config теперь два обязательных свойства, port и host. Объект без host tsc не пропустит — свойства не хватает, ошибка TS2741. На слиянии построено дополнение чужих интерфейсов. Например, в файле без import и export к описанию окна браузера Window добавляют своё свойство, объявив ещё один interface Window.

Что выбрать

Строгого правила нет, и команда выбирает один стиль. Часто договариваются так:

  • форма объекта — interface, его можно расширить через extends и дополнить слиянием;
  • объединения, кортежи и наборы литералов — type, ведь интерфейс их не опишет.

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

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

К псевдониму хотели добавить свойство вторым объявлением, как у интерфейса:

TypeScript
type User = { name: string };
type User = { age: number }; // ✗
TEXT
error TS2300: Duplicate identifier 'User'.

Сообщение значит «имя User объявлено дважды», и tsc ставит его на обе строки. Псевдонимы не сливаются: второй этикетки с тем же названием не бывает. Если нужен тип с обоими свойствами, объявите интерфейс дважды или соберите новый тип через &: type Person = User & { age: number };.

Главное

  • Для формы объекта type и interface почти одинаковы: те же проверки, те же ? и readonly, оба стираются.
  • Объединения и кортежи называют только через type.
  • Два интерфейса с одним именем сливаются в один, а два type с одним именем — ошибка TS2300.
  • Команда выбирает один стиль; частое правило — interface для объектов, type для остального.