Avatar ·

TypeScript: В чем разница между interface и type? Когда что использовать?

Всем привет! Начал изучать TypeScript и запутался в разнице между `interface` и `type`. Вроде бы оба позволяют описывать структуру объектов, но я не понимаю, когда какой инструмент применять. Посмотрите на мой код. Я пытаюсь описать пользователя и его адрес: ```typescript // Вариант с interface interface IUser { name: string; age: number; address: IAddress; } interface IAddress { city: string; street: string; } // Вариант с type type TUser = { name: string; age: number; address: TAddress; } type TAddress = { city: string; street: string; } ``` Оба варианта работают, но я слышал, что есть какие-то фундаментальные отличия. Например, я видел, что `type` может делать что-то с объединениями, а `interface` — нет. И еще в некоторых проектах используют `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';

Главное правило — будьте последовательны в рамках одного проекта. Если команда договорилась использовать интерфейсы для моделей данных — используйте их везде, где это возможно.