TypeScript: Interfaces vs Types — что выбрать в 2024?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: вечный спор TypeScript-разработчиков

Если вы хоть раз писали на TypeScript, то наверняка сталкивались с вопросом: "Когда использовать interface, а когда type?" Этот вопрос собрал тысячи просмотров на Stack Overflow и стал настоящим камнем преткновения для новичков. Давайте раз и навсегда разберемся в этой дилемме.

TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. И у нас есть два основных инструмента для описания структуры данных: Interface и Type Alias. На первый взгляд они делают одно и то же, но дьявол кроется в деталях. В этой статье мы не просто перечислим различия — мы покажем их на реальных примерах и дадим четкие рекомендации.

Что такое Interface?

Interface — это способ описать структуру объекта. Он как чертеж для данных: вы говорите TypeScript, какие свойства и методы должен иметь объект.

// Базовый интерфейс
interface User {
    name: string;
    age: number;
    email?: string; // необязательное свойство
    readonly id: number; // только для чтения
}

// Использование
const user: User = {
    name: "Анна",
    age: 25,
    id: 1
};

Интерфейсы могут наследоваться друг от друга, что делает их идеальными для описания иерархий:

interface Animal {
    name: string;
    sound(): void;
}

interface Dog extends Animal {
    breed: string;
    wagTail(): void;
}

// Теперь Dog содержит все поля Animal + свои
const myDog: Dog = {
    name: "Рекс",
    breed: "Овчарка",
    sound() { console.log("Гав!"); },
    wagTail() { console.log("Виляет хвостом"); }
};

Что такое Type Alias?

Type Alias (псевдоним типа) — это более гибкий инструмент. Он может описывать не только объекты, но и примитивы, объединения и пересечения типов.

// Тип для примитива
type ID = string | number;

// Тип для объекта
type UserType = {
    name: string;
    age: number;
};

// Тип для функции
type Greeting = (name: string) => string;

// Union Type (объединение)
type Status = "active" | "inactive" | "pending";

// Intersection Type (пересечение)
type Person = { name: string };
type Employee = Person & { salary: number };

Главная суперсила типов — union types. Интерфейсы такое не умеют:

// ❌ Так нельзя с interface
// interface Status = "active" | "inactive"; // Ошибка!

// ✅ А с type — легко!
type Status = "active" | "inactive" | "pending";

function processStatus(status: Status) {
    switch (status) {
        case "active":
            console.log("Активен");
            break;
        case "inactive":
            console.log("Неактивен");
            break;
        case "pending":
            console.log("В ожидании");
            break;
    }
}

Ключевые различия: Interface vs Type

Давайте разложим по полочкам, чем они отличаются на практике.

1. Объединение и пересечение типов

Type поддерживает union (|) и intersection (&) напрямую. Interface — только через extends.

// Union — только type
type Success = { data: string };
type Error = { error: string };
type Result = Success | Error; // Можно!

// Intersection
type Full = Success & Error; // Можно!

// Для interface пришлось бы создавать отдельные интерфейсы
interface IResult extends Success, Error {} // Работает, но громоздко

2. Declaration merging (слияние объявлений)

Это уникальная фича интерфейсов. Если вы объявите два интерфейса с одним именем, TypeScript объединит их.

// Первый интерфейс
interface User {
    name: string;
}

// Второй интерфейс с тем же именем
interface User {
    age: number;
}

// Результат: User = { name: string; age: number; }
const user: User = {
    name: "Иван",
    age: 30
};

С типами такое не пройдет — будет ошибка:

type User = { name: string };
type User = { age: number }; // ❌ Duplicate identifier 'User'

3. Работа с примитивами

Type может создавать псевдонимы для примитивов, интерфейсы — нет.

type UserID = number; // ✅ Можно
type Callback = (x: number) => void; // ✅ Можно

// interface UserID = number; // ❌ Нельзя
// interface Callback = (x: number) => void; // ❌ Нельзя

4. Mapped Types (маппинг типов)

Type поддерживает маппинг — мощный инструмент для трансформации типов:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type Optional<T> = {
    [P in keyof T]?: T[P];
};

interface Person {
    name: string;
    age: number;
}

type ReadonlyPerson = Readonly<Person>;
// { readonly name: string; readonly age: number; }

type OptionalPerson = Optional<Person>;
// { name?: string; age?: number; }

Интерфейсы с этим справляются хуже — приходится использовать утилитарные типы.

Когда что выбирать? Практические рекомендации

Теперь самый важный вопрос: что использовать в реальных проектах? Вот мой чек-лист:

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

  • Вы пишете библиотеку — интерфейсы лучше расширяются пользователями через declaration merging.
  • Нужна иерархия классов — интерфейсы отлично сочетаются с implements.
  • Вы работаете с объектами — интерфейсы более читаемы для описания структур.
  • Важно наследование — extends работает интуитивно понятнее.

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

  • Нужны union или intersection — это главное преимущество типов.
  • Работаете с примитивами — создавайте псевдонимы для строк, чисел и т.д.
  • Нужны сложные вычисляемые типы — mapped types, conditional types.
  • Пишете утилиты — типы лучше подходят для обобщенных решений.

Реальный пример из жизни

Представьте, что вы пишете систему управления задачами. Вот как может выглядеть типизация с использованием обоих подходов:

// Базовые типы
type TaskStatus = "todo" | "in_progress" | "done";
type Priority = "low" | "medium" | "high";

// Интерфейс для задачи
interface Task {
    id: string;
    title: string;
    description?: string;
    status: TaskStatus;
    priority: Priority;
    createdAt: Date;
}

// Функция для обновления статуса
function updateTaskStatus(
    task: Task, 
    newStatus: TaskStatus
): Task {
    return { ...task, status: newStatus };
}

// Union для результата операции
type TaskResult = 
    | { success: true; task: Task }
    | { success: false; error: string };

// Пример использования
function createTask                

Блоги

Рекомендаций по книгам