Avatar ·

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

Всем привет! Я только начал изучать TypeScript и наткнулся на два способа описания структуры объектов: `interface` и `type`. Сначала я думал, что это одно и то же, но потом заметил, что они ведут себя по-разному в некоторых ситуациях. Вот пример. Я могу написать так через `interface`: ```typescript interface User { name: string; age: number; } ``` Или так через `type`: ```typescript type User = { name: string; age: number; }; ``` Оба варианта работают одинаково, когда я создаю объект: ```typescript const user: User = { name: 'Alex', age: 30 }; ``` Но потом я попытался объединить два типа. С `interface` я могу просто написать второй интерфейс с тем же именем, и они сольются: ```typescript interface User { email: string; } // Теперь User имеет name, age и email ``` А с `type` я получил ошибку: "Duplicate identifier 'User'". Тогда я узнал про `&` (intersection) для типов, но это уже другой синтаксис. Также я слышал, что `type` может делать какие-то "магические" вещи, которые `interface` не умеет. Например, я видел такой код: ```typescript type Status = 'active' | 'inactive'; type Callback = (data: string) => void; ``` Попробуйте объяснить мне, пожалуйста, в чем ключевые различия между `interface` и `type`. Когда нужно использовать одно, а когда другое? Есть ли ситуации, где они взаимозаменяемы, а где нет? Заранее спасибо! <<>>

Основные различия между interface и type в TypeScript

Отличный вопрос! Действительно, на первый взгляд interface и type выглядят взаимозаменяемыми, но у них есть важные отличия. Давайте разберем их подробно.

1. Расширение (extends vs intersection)

interface использует ключевое слово extends для наследования:

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// Dog: { name: string; breed: string; }

type использует оператор пересечения &:

type Animal = {
  name: string;
}

type Dog = Animal & {
  breed: string;
}

// Dog: { name: string; breed: string; }

2. Слияние объявлений (declaration merging)

Это ключевое отличие. interface можно объявлять несколько раз с одним именем — они автоматически объединятся:

interface User {
  name: string;
}

interface User {
  age: number;
}

// Результат: User = { name: string; age: number; }

type так делать нельзя — будет ошибка "Duplicate identifier".

3. Возможности type, которых нет у interface

type может представлять не только объекты, но и:

  • Примитивные типы: type ID = string | number;
  • Литеральные типы: type Status = 'active' | 'inactive';
  • Кортежи: type Pair = [string, number];
  • Условные типы: type IsString<T> = T extends string ? true : false;
  • Mapped types: type Readonly<T> = { readonly [K in keyof T]: T[K] };

4. Что умеет interface, но не умеет type?

Кроме слияния объявлений, interface может:

  • Наследоваться от класса: interface MyInterface extends MyClass { ... }
  • Использоваться в implements для классов: class MyClass implements MyInterface { ... }

5. Производительность и компиляция

С точки зрения производительности компиляции, interface обычно быстрее, потому что TypeScript может кэшировать результаты. type с пересечениями (&) может быть медленнее на больших проектах из-за рекурсивной проверки типов.

6. Когда что использовать?

Используйте interface, когда:

  • Вы определяете форму объекта (особенно для API/DTO)
  • Вам нужно слияние объявлений (например, для расширения сторонних типов)
  • Вы пишете библиотеку — interface лучше документируется
  • Вы работаете с классами (implements)

Используйте type, когда:

  • Вам нужны union/intersection типы
  • Вы работаете с примитивами, кортежами или литералами
  • Вам нужны условные или mapped типы
  • Вы создаете вспомогательные типы-утилиты

Пример из практики

// Хорошо для interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// Хорошо для type
type UserRole = 'admin' | 'user' | 'guest';
type ApiResult<T> = ApiResponse<T> | { error: string };

Итог

В 80% случаев для объектов можно использовать и то, и другое. Но если вы сомневаетесь — начинайте с interface, а переходите на type только когда нужны его специфические возможности. Это общепринятая практика в TypeScript-сообществе.

<<>>

Дополнение: практический подход и производительность

Хочу добавить еще один важный аспект — производительность при разработке крупных проектов.

Проблема с type и пересечениями

Когда вы используете type с оператором & для объединения множества типов, TypeScript может создавать очень сложные структуры. Например:

type A = { a: string };
type B = { b: number };
type C = A & B & { c: boolean }; // Работает, но может быть медленно при вложенности

С каждым уровнем вложенности компилятору нужно рекурсивно проверять все свойства. В проектах с сотнями типов это может заметно замедлить работу IDE и компиляцию.

Как избежать проблем

  • Для простых объектов используйте interface — он быстрее и понятнее
  • Если вам нужно объединить 3+ типа, подумайте, не лучше ли создать отдельный интерфейс
  • Для сложных условных типов используйте type, но старайтесь не делать глубоких вложенностей

Еще один лайфхак

Иногда можно комбинировать оба подхода:

// Базовый интерфейс
interface BaseEntity {
  id: number;
  createdAt: Date;
}

// Тип для расширения
type UserEntity = BaseEntity & {
  name: string;
  email: string;
};

Так вы получаете преимущества интерфейса (читаемость, производительность) и гибкость типа (быстрое расширение).

Главное правило: не усложняйте там, где можно обойтись простым interface. TypeScript — это про читаемость и безопасность, а не про "магию" типов.