Слияние объявлений — это мощная возможность TypeScript, позволяющая объединять несколько объявлений с одинаковым именем в одно определение.
Это позволяет поэтапно выстраивать сложные типы и безопасно расширять существующие типы с сохранением типобезопасности.
Ключевые преимущества
- Постепенное улучшение: построение типов поэтапно через несколько объявлений.
- Расширяемость: добавление новых членов в существующие типы без изменения исходных определений.
- Организация: разбиение крупных определений типов на логические группы.
- Совместимость: расширение сторонних определений типов при необходимости.
Распространённые сценарии использования
- расширение встроенных типов и типов из сторонних библиотек;
- добавление типовой информации для JavaScript‑библиотек;
- организация крупных интерфейсов в нескольких файлах;
- создание плавных API с цепочкой вызовов методов;
- реализация шаблона дополнения модулей.
Слияние интерфейсов
Интерфейсы с одинаковым именем объединяются автоматически.
Пример
// Первое объявление
interface Person {
name: string;
age: number;
}
// Второе объявление с тем же именем
interface Person {
address: string;
email: string;
}
// TypeScript объединяет их в:
// interface Person {
// name: string;
// age: number;
// address: string;
// email: string;
// }
const person: Person = {
name: "John",
age: 30,
address: "123 Main St",
email: "john@example.com"
};
console.log(person);
Перегрузки функций с слиянием
Можно определить несколько объявлений функций, которые затем объединяются при реализации.
Пример
// Перегрузки функции
function processValue(value: string): string;
function processValue(value: number): number;
function processValue(value: boolean): boolean;
// Реализация, обрабатывающая все перегрузки
function processValue(value: string | number | boolean): string | number | boolean {
if (typeof value === "string") {
return value.toUpperCase();
} else if (typeof value === "number") {
return value * 2;
} else {
return !value;
}
}
// Использование функции с разными типами
console.log(processValue("hello")); // "HELLO"
console.log(processValue(10)); // 20
console.log(processValue(true)); // false
Слияние пространств имён
Пространства имён с одинаковым именем объединяются.
Пример
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
}
namespace Validation {
export interface NumberValidator {
isValid(n: number): boolean;
}
export class ZipCodeValidator implements StringValidator {
isValid(s: string): boolean {
return s.length === 5 && /^\d+$/.test(s);
}
}
}
// После слияния:
// namespace Validation {
// export interface StringValidator { isValid(s: string): boolean; }
// export interface NumberValidator { isValid(n: number): boolean; }
// export class ZipCodeValidator implements StringValidator { ... }
// }
// Использование объединённого пространства имён
const zipValidator = new Validation.ZipCodeValidator();
console.log(zipValidator.isValid("12345")); // true
console.log(zipValidator.isValid("1234")); // false
console.log(zipValidator.isValid("abcde")); // false
Слияние классов и интерфейсов
Объявление класса может объединяться с интерфейсом того же имени.
Пример
// Объявление интерфейса
interface Cart {
calculateTotal(): number;
}
// Объявление класса с тем же именем
class Cart {
items: { name: string; price: number }[] = [];
addItem(name: string, price: number): void {
this.items.push({ name, price });
}
// Должно реализовывать метод интерфейса
calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
// Использование объединённых класса и интерфейса
const cart = new Cart();
cart.addItem("Book", 15.99);
cart.addItem("Coffee Mug", 8.99);
console.log(`Total: $${cart.calculateTotal().toFixed(2)}`);
Слияние перечислений (enum)
Объявления перечислений с одинаковым именем объединяются.
Пример
// Первая часть перечисления
enum Direction {
North,
South
}
// Вторая часть перечисления
enum Direction {
East = 2,
West = 3
}
// После слияния:
// enum Direction {
// North = 0,
// South = 1,
// East = 2,
// West = 3
// }
console.log(Direction.North); // 0
console.log(Direction.South); // 1
console.log(Direction.East); // 2
console.log(Direction.West); // 3
// Также можно обращаться по значению
console.log(Direction[0]); // "North"
console.log(Direction[2]); // "East"
Дополнение модулей
Можно расширять существующие модули или библиотеки, объявляя дополнительные типы и функциональность.
Пример
// Исходное определение библиотеки
// Предположим, это из сторонней библиотеки
declare namespace LibraryModule {
export interface User {
id: number;
name: string;
}
export function getUser(id: number): User;
}
// Расширение дополнительной функциональностью (ваш код)
declare namespace LibraryModule {
// Добавить новый интерфейс
export interface UserPreferences {
theme: string;
notifications: boolean;
}
// Добавить новое свойство в существующий интерфейс
export interface User {
preferences?: UserPreferences;
}
// Добавить новую функцию
export function getUserPreferences(userId: number): UserPreferences;
}
// Использование дополненного модуля
const user = LibraryModule.getUser(123);
console.log(user.preferences?.theme);
const prefs = LibraryModule.getUserPreferences(123);
console.log(prefs.notifications);
Лучшие практики
При использовании слияния объявлений следует учитывать несколько правил:
- Порядок важен для перегрузок функций: сигнатура реализации должна быть наиболее общей.
- Несовпадающие члены должны быть совместимы: если два интерфейса объявляют свойство с одинаковым именем, типы должны быть идентичны или совместимы.
- Поздние интерфейсы имеют приоритет: при конфликтах в объединённых интерфейсах побеждает последнее объявление.
- Приватные и защищённые члены: классы не могут объединяться, если у них есть приватные или защищённые члены с одинаковым именем, но разными типами.
- Экспорт пространств имён: после слияния вне пространства имён видны только экспортированные объявления.
Соображения по производительности
- Время компиляции: чрезмерное слияние объявлений может увеличить время компиляции.
- Проверка типов: сложные объединённые типы могут влиять на производительность IDE.
- Размер сборки: слияние объявлений не влияет на производительность во время выполнения или размер сборки.
Советы по оптимизации
- сохраняйте объединённые интерфейсы сфокусированными и согласованными;
- избегайте глубокой вложенности в объединённых типах;
- используйте псевдонимы типов для простых комбинаций типов вместо слияния.