Перейти к содержимому

type vs interface

Топ-5 вопрос на интервью. Многие отвечают «interface для объектов, type для всего остального» — это поверхностно. Реальная разница: declaration merging, computed keys, расширение примитивов/юнионов, скорость компилятора и качество сообщений об ошибках. Красный флаг — не знать про declaration merging.

Сравнение по ключевым осям:

Свойствоtypeinterface
Declaration merging❌ Дублирование = ошибка✅ Объявления сливаются
Computed/mapped keys{ [K in Keys]: T }❌ Только литералы
Примитивы / union / tupletype Id = string | number❌ Только object shapes
Синтаксис расширенияtype B = A & { x: T }interface B extends A { x: T }
Производительность / ошибкиМожет «расклеиваться» в длинные intersectionКэшируется, ошибки читабельнее
Implements у классов✅ если это shape

Declaration merging — главная фича interface: критично для расширения lib.d.ts и сторонних API.

// Declaration merging работает у interface
interface Window {
myAnalytics: { track(e: string): void };
}
window.myAnalytics.track('login'); // ок
// type — нельзя дублировать
type User = { id: string };
// type User = { name: string }; // Error: Duplicate identifier 'User'.

Computed / mapped keys и юниони — только через type:

type Keys = 'create' | 'read' | 'update' | 'delete';
// mapped type — ТОЛЬКО через type
type Permissions = { [K in Keys]: boolean };
// union / tuple / primitive alias
type Id = string | number;
type Pair<A, B> = readonly [A, B];
// extends-style: для type — пересечение
type Animal = { name: string };
type Dog = Animal & { breed: string };

Практическое правило: для публичных API библиотек используй interface (из-за merging и понятных ошибок). Для маппингов, юнионов и tuple — type.

Итог: interface — для расширяемых object shapes; type — для всего, что не object: union, tuple, primitive alias, mapped types.