Что такое declaration merging в TypeScript?

SeniorTypeScript · Frontend·Обновлено 17 августа 2026
Коротко
Declaration merging — это механизм компилятора TypeScript, при котором несколько объявлений с одинаковым именем (interface, namespace, class+namespace и т.д.) автоматически объединяются в одну сущность на уровне системы типов, без генерации дополнительного JS-кода.

Суть механизма

Declaration merging — это поведение компилятора TypeScript, при котором два и более объявления с одинаковым именем в одной области видимости не конфликтуют, а сливаются компилятором в единое определение типа. Это происходит на этапе проверки типов, а не в рантайме: для interface и module augmentation никакого JS-кода не генерируется вообще, сливается только описание типов. Исключение — namespace, который компилируется в реальный объект в рантайме, поэтому его слияние видно и в скомпилированном JS.

Какие сущности можно сливать

  • interface + interface — самый частый случай. Все члены объединяются в один тип, конфликтующие по имени, но несовместимые по сигнатуре свойства дают ошибку компиляции.
  • namespace + namespace — содержимое объединяется, экспортируемые члены становятся доступны через общее имя.
  • namespace + class / function / enum — классический паттерн для добавления статических членов или вспомогательных типов к классу/функции без изменения их основного объявления.
  • interface + class — интерфейс с тем же именем, что и класс, расширяет тип экземпляра класса (не сам класс как значение).

Порядок слияния для interface

Если в интерфейсе несколько раз объявлен один и тот же метод с разными сигнатурами (function overloads), более поздние объявления имеют больший приоритет при разрешении перегрузок, а объявления из later-merged interface стоят выше объявлений из более раннего.

Module augmentation

Один из главных практических кейсов senior-уровня — расширение типов сторонних библиотек через declare module. Например, добавление кастомных полей в Express.Request или расширение глобального Window. Это тоже частный случай declaration merging: TypeScript находит существующее объявление модуля/интерфейса и добавляет к нему новые члены.

Зачем это нужно на практике

  • Типизация legacy-библиотек и глобальных объектов (window, process.env)
  • Паттерн jQuery-style API: класс/функция + статические утилиты через namespace
  • Расширение типов Express, Redux, Vue-плагинов без правки node_modules
  • Постепенное дополнение больших публичных API (например, у самого TypeScript compiler API)

Ограничения

  • Нельзя сливать type alias с другим type или с interface — merging работает только для деклараций, а не для type alias
  • Классы нельзя сливать друг с другом (только class+namespace или class+interface)
  • Несовместимые сигнатуры свойств при слиянии интерфейсов вызывают ошибку компиляции

Что хочет услышать интервьюер

Понимание, что merging происходит на уровне типов, а не в рантайме (кроме namespace)

Знание конкретных комбинаций: interface+interface, namespace+class, module augmentation

Практический пример — расширение Express.Request или глобального объекта через declare module

Понимание, что type alias не участвует в declaration merging, в отличие от interface

Осознание ограничений: конфликтующие несовместимые сигнатуры дают ошибку компиляции

Пример: Слияние interface + interface

interface User {
  id: number;
}

interface User {
  name: string;
}

// Итоговый тип User содержит оба поля
const u: User = { id: 1, name: 'Anton' };

Пример: namespace + class для статических членов

class Album {
  label: Album.AlbumLabel;
}

namespace Album {
  // Вложенный тип доступен как Album.AlbumLabel
  export class AlbumLabel {}
}

const a = new Album();
a.label = new Album.AlbumLabel();

Пример: Module augmentation: расширение Express.Request

// types/express/index.d.ts
import 'express';

declare module 'express' {
  interface Request {
    // добавляем поле, которое кладёт кастомный middleware
    userId?: string;
  }
}

// использование в обработчике
app.use((req, res, next) => {
  req.userId = '123'; // теперь TypeScript знает про это поле
  next();
});

Типичные ошибки

Путают declaration merging с обычным наследованием через extends

Считают, что type alias можно так же 'доопределить' повторным объявлением, как interface

Не знают про паттерн namespace + class/function для добавления статических членов

Не могут объяснить, зачем вообще нужен module augmentation на реальном примере

Думают, что слияние всегда генерирует дополнительный JS-код в рантайме

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее