interface vs type: когда что

interface и type в TypeScript имеют много общего при описании объектных типов, но отличаются по возможностям: interface поддерживает declaration merging и implements, type alias — union, intersection, conditional и mapped types.

Зачем нужно

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

Где используется

  • interface — объектные типы, которые должны расширяться или реализовываться классами
  • type — union, примитивы, кортежи, mapped types, conditional types
  • Оба — для объектных типов (в большинстве случаев взаимозаменяемы)

Основной контент

Что умеют оба

// Оба описывают объектные типы
interface UserI { name: string; age: number }
type     UserT = { name: string; age: number };

// Оба расширяются (синтаксис разный)
interface AdminI extends UserI { permissions: string }
type     AdminT = UserT & { permissions: string };

// Оба используются как типы параметров
function greet(user: UserI): void { }
function greet(user: UserT): void { }

Что умеет только interface

// 1. Declaration Merging — дополнение из нескольких мест
interface Config { host: string; }
interface Config { port: number; }
// Итог: { host: string; port: number } — type не поддерживает

// 2. Реализация классами (implements)
class Server implements Config {
  host = "localhost";
  port = 3000;
}

// 3. Рекурсивные объектные типы (без промежуточного alias)
interface TreeNode {
  value: number;
  children: TreeNode; // OK
}

Что умеет только type

// 1. Union types
type ID = string | number;
type Status = "active" | "inactive" | "pending";
// interface не может: interface ID = string | number // Error

// 2. Кортежи как именованный тип
type Pair = [string, number];
type RGB  = [red: number, green: number, blue: number];

// 3. Mapped types
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };

// 4. Conditional types
type IsString<T> = T extends string ? true : false;

// 5. Computed type (вычисляемые через утилиты)
type UserKeys = keyof { name: string; email: string }; // "name" | "email"

Производительность: interface vs type

// interface быстрее при extends цепочках
// TypeScript кэширует interface, пересчитывает type при каждом использовании
// Для объектных типов в сложных цепочках — предпочитайте interface

// type быстрее для простых псевдонимов примитивов и union
type Id = string; // просто псевдоним, нет смысла в interface

Рекомендации

// Используйте interface когда:
// - Это объектный тип, который будут расширять другие
// - Класс будет реализовывать контракт
// - Нужна возможность дополнить тип (declaration merging)
interface Repository<T> {
  findById(id: string): Promise<T | null>;
}

// Используйте type когда:
// - Union, intersection, кортеж
// - Mapped, conditional, utility type
// - Псевдоним примитива или функционального типа
type Callback = (err: Error | null, data?: unknown) => void;
type Nullable<T> = T | null;

Частые ошибки

  • Пытаться сделать union через interfaceinterface Status = "a" | "b" — синтаксическая ошибка; используйте type.
  • Ожидать merging для type — два type User вызовут ошибку дублирования; только interface поддерживает merging.
  • Думать что extends у type и interface одинаковыinterface extends проверяет совместимость и выдаёт ошибку при конфликте; type & молча создаёт never при несовместимых полях.
  • Переключать interface/type в середине проекта — выберите конвенцию и придерживайтесь её; смешивание без причины снижает читаемость.

Связанные темы

Ресурсы