type или interface
В мастерской описания замков хранят двумя способами. Одни подшивают в папку-скоросшиватель: в неё кладут только чертежи, зато новый лист можно подшить позже, и папка соберёт все листы вместе. Другие помечают этикеткой с названием. Этикетку можно наклеить на что угодно — на чертёж, на коробку «этот замок или тот», на пару предметов. Но вторую этикетку с тем же названием наклеить нельзя.
С interface и type то же самое: интерфейс — папка, псевдоним типа — этикетка. Оба описывают форму объекта, и чаще всего между ними нет разницы. Отличаются они в двух местах, и о них любят спрашивать на собеседовании: «Чем type отличается от interface?»
Для объектов одно и то же
Опишем одну форму обоими способами:
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Давайте разберём пример:
PointTypeиPointShapeописывают одну и ту же форму: числаxиy.aобъявлена какPointType, а строкаconst b: PointShape = a;всё равно проходит. Типы сравниваются по форме, а формы совпали.- Программа печатает
3.
Для формы объекта неважно, каким способом её записали:
- tsc проверяет литерал одинаково: недостающее свойство —
TS2741, лишнее —TS2353; ?иreadonlyработают в обоих;- оба стираются и в JavaScript не попадают;
- форму расширяют через
extendsу интерфейса и через&у псевдонима.
Только type: объединения и кортежи
Этикетку можно наклеить на что угодно, а папка хранит только чертежи. Псевдоним даёт имя любому типу:
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: слияние объявлений
Два объявления интерфейса с одним именем не спорят, а складываются в одно:
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, ведь интерфейс их не опишет.
Важнее всего, чтобы в одном проекте форма объекта везде описывалась одинаково. В этом курсе с этой темы формы объектов — интерфейсы.
Частая ошибка
К псевдониму хотели добавить свойство вторым объявлением, как у интерфейса:
type User = { name: string };
type User = { age: number }; // ✗error TS2300: Duplicate identifier 'User'.Сообщение значит «имя User объявлено дважды», и tsc ставит его на обе строки. Псевдонимы не сливаются: второй этикетки с тем же названием не бывает. Если нужен тип с обоими свойствами, объявите интерфейс дважды или соберите новый тип через &: type Person = User & { age: number };.
Главное
- Для формы объекта
typeиinterfaceпочти одинаковы: те же проверки, те же?иreadonly, оба стираются. - Объединения и кортежи называют только через
type. - Два интерфейса с одним именем сливаются в один, а два
typeс одним именем — ошибкаTS2300. - Команда выбирает один стиль; частое правило —
interfaceдля объектов,typeдля остального.