TypeScript: В чем разница между interface и type? Когда что использовать?
Ключевые различия между Interface и Type
Отличный вопрос! Это одна из самых частых тем недопонимания у новичков. Давайте разберем фундаментальные отличия.
1. Примитивы и объединения (Union Types)
type может описывать примитивные типы и их объединения, а interface — нет:
// ✅ type — можно
type Status = 'active' | 'inactive';
type ID = string | number;
type Callback = (data: string) => void;
// ❌ interface — так нельзя
interface Status = 'active' | 'inactive'; // Ошибка!
interface ID = string | number; // Ошибка!
2. Расширение (Extends vs Intersection)
interface использует extends, type — пересечение (&):
// Interface
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
// Type
type Animal = { name: string };
type Dog = Animal & { bark(): void };
3. Объединение деклараций (Declaration Merging)
Это самое важное отличие! interface можно объявлять несколько раз с одним именем — TypeScript объединит их:
interface User {
name: string;
}
interface User {
age: number;
}
// Результат: User = { name: string; age: number; }
type так делать нельзя — будет ошибка о дублировании имени.
4. Mapped Types и Conditional Types
Только type поддерживает продвинутые манипуляции с типами:
// Mapped type — только type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Conditional type — только type
type IsString<T> = T extends string ? 'yes' : 'no';
// Utility type с преобразованием
type PartialUser = Partial<User>; // Все поля необязательные
type PickName = Pick<User, 'name'>; // Только поле name
5. Производительность и TypeScript Compiler
При ошибках компилятор показывает разную информацию:
- Interface: показывает имя интерфейса (например,
Type 'User' is not assignable) - Type: может показать развернутое пересечение (
Type '{ name: string; } & { age: number }')
Для сложных типов с множеством пересечений interface дает более читаемые сообщения об ошибках.
Когда что использовать?
- Используйте interface для:
- Описания форм объектов, которые могут расширяться (например, в библиотеках)
- API-контрактов (DTO, ответы сервера)
- Когда нужна читаемость ошибок
- Используйте type для:
- Примитивов и их объединений (
string | number) - Кортежей (
type Pair = [string, number]) - Условных и маппированных типов
- Когда нужна композиция через пересечения (&)
- Примитивов и их объединений (
Практический пример
// Хороший пример использования interface
interface User {
id: number;
email: string;
}
// Библиотека может расширить наш интерфейс
interface User {
createdAt: Date; // Declaration merging
}
// Хороший пример использования type
type ApiResponse<T> = {
data: T;
error: string | null;
status: 'success' | 'error';
};
type UserResponse = ApiResponse<User>;
Итог: В 80% случаев для объектов используйте interface. Для всего остального (примитивы, объединения, продвинутые типы) — type. Это стандарт, принятый в сообществе TypeScript.
Дополнение: Практические рекомендации от команды TypeScript
Официальная документация TypeScript рекомендует следующий подход:
Когда выбирать type:
- Сложные композиции: Если нужно объединить несколько типов через
&или| - Утилиты: Создание generic-типов вроде
type MyPartial<T> = { [K in keyof T]?: T[K] } - Функции: Для описания сигнатур функций как типов
Когда выбирать interface:
- Объекты по умолчанию: Если вы описываете форму данных
- Расширяемость: Когда предполагается, что другие разработчики могут добавить поля
- Классы: При реализации через
implements
Совет по стилю кода:
// Плохо: смешиваем без причины
interface IUser { name: string }
type TUser = { name: string } // Избыточно
// Хорошо: используем interface для объекта
interface User { name: string }
// Хорошо: используем type для объединения
type Status = 'active' | 'inactive';
Главное правило — будьте последовательны в рамках одного проекта. Если команда договорилась использовать интерфейсы для моделей данных — используйте их везде, где это возможно.