Что такое declaration merging в TypeScript?
Суть механизма
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)
Ограничения
- Нельзя сливать
typealias с другим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-код в рантайме


