Система типов TypeScript фундаментально опирается на структурную типизацию (Structural Typing / Duck Typing): совместимость типов определяется исключительно их формой (набором свойств и методов), а не явным номинальным объявлением (как в Java или C#). Если два разных типа имеют одинаковую структуру полей, компилятор считает их взаимно заменяемыми.
Для описания форм структурных данных в TypeScript существуют два основных инструмента:
interface): открытые контракты для описания объектов и классов. Поддерживают наследование через extends и механизм автоматического слияния деклараций (Declaration Merging).type): гибкие синтаксические псевдонимы для любых сущностей системы типов (объектов, примитивов, объединений union, пересечений intersection, кортежей tuples и функциональных сигнатур).В современной разработке выбор между interface и type диктуется архитектурным контекстом: интерфейсы идеальны для расширяемых публичных контрактов библиотек и ООП-иерархий, а псевдонимы типов незаменимы при создании сложных композиционных и вычисляемых типов.
Разница между interface и type сводится к открытости (открыты для слияния) против фиксации (неизменяемое определение):
+-------------------------------------------------------------------------+
| INTERFACE vs TYPE: МЕНТАЛЬНАЯ МОДЕЛЬ |
+-------------------------------------------------------------------------+
| INTERFACE (Открытый контракт): |
| interface User { id: string } |
| interface User { name: string } ---> Итог: { id: string; name: string }|
| (Слияние деклараций Declaration Merging работает автоматически) |
+-------------------------------------------------------------------------+
| TYPE ALIAS (Замкнутый псевдоним): |
| type User = { id: string } |
| type User = { name: string } ---> ОШИБКА TS2300: Duplicate identifier|
| (Повторное объявление строго запрещено) |
+-------------------------------------------------------------------------+
| СПОСОБНОСТИ К КОМПОЗИЦИИ: |
| type Role = 'admin' | 'user' ---> Интерфейс НЕ умеет делать union! |
| type Pair = [number, string] ---> Интерфейс НЕ умеет делать кортеж! |
+-------------------------------------------------------------------------+&)
Компилятор сопоставляет список требуемых свойств целевого типа с фактическим значением.
Для каждого свойства рекурсивно проверяется совместимость типов его значений.
Для прямых объектных литералов компилятор блокирует неизвестные поля, предотвращая опечатки.
Результат проверки связи типов сохраняется во внутреннем кэше компилятора для ускорения сборки.
Что произойдет, если в разных файлах проекта объявлены два интерфейса с одинаковым именем: `interface Window { analytics: Tracker }`?
process.env в Node.js или глобального объекта window через слияние интерфейсов.req.user в базовый интерфейс запроса.IUserRepository) для реализации паттерна инверсии зависимостей (Dependency Inversion).type TenantEntity = BaseEntity & Auditable & TenantBound).extends vs &#Интерфейсы расширяются с помощью ключевого слова extends, а типы объединяются через оператор пересечения &:
// 1. Интерфейсы: наследование через extends
interface Identified {
id: string;
}
interface User extends Identified {
email: string;
role: 'admin' | 'user';
}
// 2. Type Aliases: композиция через пересечение (&)
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type FullUser = User & Timestamped;
const activeUser: FullUser = {
id: 'usr_9482',
email: '[email protected]',
role: 'admin',
createdAt: new Date(),
updatedAt: new Date(),
};Интерфейс строго проверяет совместимость переопределяемых свойств. При несовместимости компилятор выдает ошибку сразу. В случае type & компилятор создает тип never для конфликтующего свойства:
interface BaseNode {
id: string;
}
// ОШИБКА: interface явно запрещает несовместимое переопределение
// interface NumberNode extends BaseNode {
// id: number; // TS2430: Interface 'NumberNode' incorrectly extends interface 'BaseNode'.
// }
// В Type Alias пересечение несовместимых примитивов создает 'never':
type Alpha = { id: string };
type Beta = { id: number };
type Combined = Alpha & Beta;
// Поле id имеет тип: string & number === never!
// const bad: Combined = { id: 123 }; // ОШИБКА: Type 'number' is not assignable to type 'never'.type, но не умеет interface#Псевдонимы типов могут представлять примитивы, кортежи, объединения и вычисляемые свойства (Mapped Types):
// Объединения (Unions)
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
// Кортежи (Tuples)
type Coordinate = [latitude: number, longitude: number];
// Примитивные псевдонимы
type UUID = string;
// Извлечение типов свойств (Indexed Access)
type UserRole = User['role']; // 'admin' | 'user'
// Отображенные типы (Mapped Types) - внутри интерфейса запрещены!
type ReadonlyRecord<T> = {
readonly [K in keyof T]: T[K];
};Почему выражение `const user: User = { id: '1', email: '[email protected]', extra: true }` вызывает ошибку, но при передаче через промежуточную переменную `const temp = { id: '1', email: '[email protected]', extra: true }; const user: User = temp;` компилятор молчит?
Если две разные команды объявляют интерфейс UserConfig в глобальной области видимости, TypeScript сольет их поля воедино. Это может маскировать скрытые баги и коллизии.
Решение: Используйте type для внутренних структур или упаковывайте интерфейсы в именованные ES-модули с export.
При слепом слиянии типов через & легко создать свойства типа never:
type ResA = { status: 200; data: string };
type ResB = { status: 404; error: string };
type Res = ResA & ResB; // Поле status стало (200 & 404) === never!Для взаимоисключающих состояний всегда используйте объединение (|), а не пересечение (&).