type или interface: в чём разница
Собеседник показывает два почти одинаковых куска кода — один с type, другой с interface — и спрашивает: «Какая разница? И вообще, зачем в языке два способа сделать одно и то же?» Вопрос кажется простым, но именно на нём чаще всего путаются.
type (псевдоним типа) — способ дать имя любому типу: объекту, союзу, кортежу, примитиву. interface — способ описать форму объекта, заточенный именно под объекты и умеющий «дополняться» повторными объявлениями.
Вопрос-крючок
Вот два объявления, которые выглядят как будто взаимозаменяемо:
type UserType = {
name: string;
age: number;
};
interface UserInterface {
name: string;
age: number;
}
const a: UserType = { name: "Аня", age: 20 };
const b: UserInterface = { name: "Боря", age: 22 };
console.log(a.name, b.name);Вывод:
Аня БоряОба варианта работают одинаково, оба ловят опечатки в полях, оба дают автодополнение в редакторе. На первый взгляд — просто два синонима. Но если копнуть чуть глубже, различия всплывают быстро, и именно про них спрашивают дальше.
Разница №1: type — это имя для чего угодно, interface — только для формы объекта
Ключевое отличие: interface умеет описывать только объекты (и функции, и классы — но всё равно через объектную форму). А type — это просто «ярлык», который можно приклеить к любому типу вообще:
// Это работает — type может быть чем угодно
type ID = string | number;
type Point = [number, number];
type Callback = (value: string) => void;
type Status = "loading" | "success" | "error";
// А вот так interface не сделать — у него нет смысла для союзов и кортежей
// interface ID = string | number; // ошибка синтаксиса, так не пишутЕсли тебе нужно назвать союз строк, кортеж координат или тип функции — это работа для type. interface тут просто не подходит по смыслу: он всегда описывает «объект с такими-то полями», ничего больше.
Разница №2: расширение через extends против пересечения через &
Оба способа умеют «наследоваться» от других типов, но делают это по-разному синтаксически. У interface есть ключевое слово extends:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const rex: Dog = { name: "Рекс", breed: "овчарка" };
console.log(rex.name, rex.breed);Вывод:
Рекс овчаркаУ type своего extends нет — вместо этого используют пересечение через &, которое буквально «склеивает» два типа в один, объединяя все их поля:
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
breed: string;
};
const rexType: DogType = { name: "Джек", breed: "лабрадор" };
console.log(rexType.name, rexType.breed);Вывод:
Джек лабрадорРезультат одинаковый на вид, но механика разная. extends у интерфейса — это именно наследование: компилятор проверяет совместимость полей и ругается, если новое поле конфликтует по типу со старым. & у type — более механическое объединение: он просто берёт все поля из обеих частей. Если поля совпадают по имени, но конфликтуют по типу (например, одно ожидает string, другое number), результат превращается в тип never — то есть в объект, которому теперь буквально невозможно присвоить ни одно значение, потому что оно должно быть одновременно и строкой, и числом.
Разница №3: дублирование имени — declaration merging
Вот особенность, которая реально ловит на собеседовании. Если объявить два interface с одинаковым именем — они автоматически объединятся в один, со всеми полями сразу:
interface Config {
debug: boolean;
}
interface Config {
port: number;
}
const cfg: Config = { debug: true, port: 3000 };
console.log(cfg.debug, cfg.port);Вывод:
true 3000Это называется declaration merging (слияние объявлений) — оно не баг, а фича, специально сделанная для расширения чужих типов (например, библиотечных). А вот если попробовать объявить два type с одинаковым именем в той же области видимости — компилятор сразу выдаст ошибку дублирования идентификатора. type — это константа-имя, её нельзя переобъявить, а interface — открытая структура, которая копит объявления.
Как это работает под капотом
На уровне компилятора TypeScript оба способа в итоге превращаются в одну и ту же внутреннюю структуру описания формы объекта — так называемый «объектный тип». Разница целиком в том, как к этой структуре можно добавлять информацию:
| Что нужно | type | interface |
| Именовать союз/кортеж/функцию | Да | Нет, только объект |
| Расширение | Через & (пересечение) | Через extends (наследование) |
| Повторное объявление с тем же именем | Ошибка | Слияние полей (declaration merging) |
| Итоговый JS-код после компиляции | Полностью исчезает | Полностью исчезает |
Последняя строка таблицы важна: и type, и interface — это чисто время компиляции. В скомпилированном JavaScript от них не остаётся вообще ничего, ни одной строчки. Это просто подсказки для компилятора и редактора, которые полностью стираются, когда код превращается в обычный JS.
Частые ошибки на собеседовании
- «interface быстрее компилируется, поэтому его нужно использовать всегда». На больших проектах это иногда действительно чуть влияет на скорость проверки типов, но это не повод отказываться от type там, где нужен союз или кортеж — там interface просто не подходит по смыслу.
- «type и interface — это одно и то же, просто разный синтаксис». Для простого объекта — почти да, но declaration merging и разные механизмы расширения — это реальные, а не косметические различия.
- Пытаются объявить union через interface. Кандидат пишет что-то вроде «interface Status = "loading" | "error"» и не понимает, почему компилятор ругается на синтаксис. interface не умеет именовать союзы — только type.
- Не знают, что произойдёт при конфликте полей в пересечении. Если два пересекаемых типа в type задают одному полю разные примитивные типы, поле не станет «одним из двух» — оно станет never, и присвоить ему что-либо станет невозможно.
Итоги
- type — универсальный псевдоним для любого типа: объекта, союза, кортежа, функции, примитива.
- interface — только для описания формы объекта, зато умеет расширяться через extends и сливаться при повторном объявлении (declaration merging).
- У type расширение делается через пересечение &, у interface — через extends; при конфликте полей в пересечении результат может схлопнуться в never.
- Оба способа исчезают без следа при компиляции в JavaScript — это чисто инструмент проверки типов на этапе разработки.
- На практике: interface — для публичных API и объектов, которые могут расширяться извне; type — для союзов, кортежей и составных типов.