TypeScriptMiddleTechnical

В чём разница между расширением одного interface другим и использованием intersection types?

extends проверяет совместимость конфликтующих полей на этапе объявления и поддерживает declaration merging; intersection тихо превращает конфликтующие поля в never и подходит для произвольной композиции типов.

Наследование интерфейса через extends

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

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

interface Dog extends Animal {
  breed: string;
  // age: string;  // ошибка: string не совместим с number
}

const d: Dog = { name: "Rex", age: 3, breed: "Husky" };

Intersection types (&)

Intersection создаёт новый тип, объединяющий все свойства нескольких типов. При конфликте типов одного поля результирующее поле получает тип never — без каких-либо ошибок на уровне объявления:

type A = { name: string; age: number };
type B = { name: string; age: string };  // конфликт age

type AB = A & B;
// AB.age имеет тип never (number & string)

const x: AB = {
  name: "test",
  age: 42 as never,  // приходится обходить, что скрывает баг
};

Ключевые различия

  • Проверка совместимости: extends проверяет конфликты сразу, intersection — нет. Конфликтующие поля тихо становятся never.
  • Декларативное слияние: интерфейсы поддерживают declaration merging (два interface Foo в разных файлах сливаются), type-алиасы — нет.
  • Применимость: interface extends работает только с интерфейсами и классами. Intersection работает с любыми типами, включая примитивы и объединения.
  • Читаемость в IDE: extends даёт чистую иерархию, которую легко читать в тултипах. Intersection разворачивается в плоский тип.
  • Производительность компилятора: глубокие цепочки intersection могут замедлять type-checking на больших проектах.

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

// Предпочтительно для ООП-иерархий и публичных API
interface Shape {
  color: string;
}
interface Circle extends Shape {
  radius: number;
}

// Предпочтительно для композиции несвязанных типов
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithId = { id: string };

type Entity = WithId & WithTimestamps;

// Миксин-паттерн (только через intersection)
type Serializable = { serialize(): string };
type Loggable = { log(): void };

type Service = Entity & Serializable & Loggable;

Ещё один важный нюанс: методы vs call signatures

// extends: метод ковариантен
interface ReadonlyArray<T> {
  map<U>(fn: (v: T) => U): U[];
}

// intersection с function types: тип вычисляется через overload
type Fn = ((x: string) => string) & ((x: number) => number);
// Fn — перегруженная функция

Подводные камни

  • Конфликт типов поля при intersection даёт never без предупреждения, что обнаруживается только при попытке присвоить значение.
  • Declaration merging интерфейсов может неожиданно сработать в монорепо, если два пакета объявляют одноимённый интерфейс в глобальном пространстве.
  • Intersection глубоко вложенных объектов не делает глубокое слияние — вложенные объекты остаются отдельными типами.
  • extends нельзя применить к union type (interface A extends B | C — ошибка), intersection — можно.
  • При использовании intersection с class в качестве типа теряется возможность проверки instanceof.
  • Сложные intersection замедляют автодополнение в VSCode на файлах с большим числом типов.

Common mistakes

  • Смешивать «interface extends и intersection types» с похожим механизмом без критерия выбора.
  • Игнорировать риск: неверно оценить границы применения темы «interface extends и intersection types» и получить хрупкое решение.
  • Показывать только синтаксис и не объяснять поведение в runtime или сборке.

What the interviewer is testing

  • Объясняет два способа композиции объектных типов с разным поведением конфликтов.
  • Показывает на примере, как работает: extends явно наследует объектный контракт и понятнее для публичных interface, а intersection пересекает требования и может привести к never при несовместимых свойствах.
  • Называет production-нюанс и граничный случай для темы «interface extends и intersection types».

Sources

Related topics