TypeScript Пространства имен

Пространства имён в TypeScript (ранее известные как "внутренние модули") предоставляют мощный механизм для организации кода и предотвращения конфликтов имён — они создают контейнер для логически связанной функциональности.

Они помогают структурировать крупные кодовые базы и грамотно управлять областью видимости, обеспечивая чистоту и удобство сопровождения кода.


Ключевые концепции

  • Логическое группирование: объединение связанного кода в именованные контейнеры.
  • Управление областью видимости: контроль доступности элементов кода.
  • Предотвращение коллизий имён: исключение конфликтов между компонентами с одинаковыми именами.
  • Организация кода: иерархическая структура для крупных приложений.

Когда использовать пространства имён

  • При организации кода в крупных унаследованных (legacy) приложениях.
  • При работе с глобальными библиотеками.
  • В процессе миграции со старых кодовых баз JavaScript.
  • Когда требуется, чтобы код был доступен глобально.

Важно: Хотя пространства имён по‑прежнему полностью поддерживаются в TypeScript, в современных приложениях обычно предпочитают ES‑модули (import/export) — они обеспечивают лучшую модульность и поддержку удаления мертвого кода (tree‑shaking).

Тем не менее понимание пространств имён ценно для сопровождения унаследованных кодовых баз и в некоторых сценариях разработки библиотек.


Базовый синтаксис пространств имён

Создание и использование пространств имён

Пространство имён определяется с помощью ключевого слова namespace:

Пример


namespace Validation {
  // Всё внутри этого блока принадлежит пространству имён Validation
  // Экспортируем то, что должно быть доступно вне пространства имён
  export interface StringValidator {
    isValid(s: string): boolean;
  }

  // Это приватно для пространства имён (не экспортируется)
  const lettersRegexp = /^[A-Za-z]+$/;

  // Экспортируемый класс — доступен вне пространства имён
  export class LettersValidator implements StringValidator {
    isValid(s: string): boolean {
      return lettersRegexp.test(s);
    }
  }

  // Ещё один экспортируемый класс
  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^[0-9]+$/.test(s) && s.length === 5;
    }
  }
}

// Использование членов пространства имён
let letterValidator = new Validation.LettersValidator();
let zipCodeValidator = new Validation.ZipCodeValidator();

console.log(letterValidator.isValid("Hello")); // true
console.log(letterValidator.isValid("Hello123")); // false
console.log(zipCodeValidator.isValid("12345")); // true
console.log(zipCodeValidator.isValid("1234")); // false — неверная длина


Расширенные возможности пространств имён

Вложенные пространства имён

Пространства имён можно вкладывать друг в друга для создания иерархической структуры:

Пример


namespace App {
  export namespace Utils {
    export function log(msg: string): void {
      console.log(`[LOG]: ${msg}`);
    }

    export function error(msg: string): void {
      console.error(`[ERROR]: ${msg}`);
    }
  }

  export namespace Models {
    export interface User {
      id: number;
      name: string;
      email: string;
    }

    export class UserService {
      getUser(id: number): User {
        return { id, name: "John Doe", email: "john@example.com" };
      }
    }
  }
}

// Использование вложенных пространств имён
App.Utils.log("Application starting");

const userService = new App.Models.UserService();
const user = userService.getUser(1);

App.Utils.log(`User loaded: ${user.name}`);


// Это вызовет ошибку типа в TypeScript
// App.log("directly accessing log"); // Ошибка — log не является прямым членом App

Псевдонимы для пространств имён

Можно создавать псевдонимы для пространств имён или их членов, чтобы упростить работу с длинными именами.

Пример


namespace VeryLongNamespace {
  export namespace DeeplyNested {
    export namespace Components {
      export class Button {
        display(): void {
          console.log("Button displayed");
        }
      }
      export class TextField {
        display(): void {
          console.log("TextField displayed");
        }
      }
    }
  }
}

// Без псевдонима — очень громоздко
const button1 = new VeryLongNamespace.DeeplyNested.Components.Button();
button1.display();

// С псевдонимом пространства имён
import Components = VeryLongNamespace.DeeplyNested.Components;
const button2 = new Components.Button();
button2.display();

// С псевдонимом конкретного члена
import Button = VeryLongNamespace.DeeplyNested.Components.Button;
const button3 = new Button();
button3.display();


Работа с многофайловыми пространствами имён

Разделение пространств имён по файлам

В крупных приложениях часто требуется разбивать код на несколько файлов.

Пространства имён TypeScript можно разделять по файлам и объединять на этапе компиляции с помощью ссылочных комментариев.

Использование ссылочных комментариев

Ссылочные комментарии помогают TypeScript понять связи между файлами.

Файл validators.ts:

Пример


namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }
}

Файл letters-validator.ts (расширяет пространство имён Validation):

Пример


/// <reference path="validators.ts" />
namespace Validation {
  const lettersRegexp = /^[A-Za-z]+$/;

  export class LettersValidator implements StringValidator {
    isValid(s: string): boolean {
      return lettersRegexp.test(s);
    }
  }
}

Файл zipcode-validator.ts:

Пример


/// <reference path="validators.ts" />
namespace Validation {
  const zipCodeRegexp = /^[0-9]+$/;

  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return zipCodeRegexp.test(s) && s.length === 5;
    }
  }
}

Файл main.ts:

Пример


/// <reference path="validators.ts" />
/// <reference path="letters-validator.ts" />
/// <reference path="zipcode-validator.ts" />

// Теперь можно использовать валидаторы из нескольких файлов
let validators: { [s: string]: Validation.StringValidator } = {};
validators["letters"] = new Validation.LettersValidator();
validators["zipcode"] = new Validation.ZipCodeValidator();

// Примеры для проверки
let strings = ["Hello", "98052", "101"];

// Проверяем каждую строку
strings.forEach(s => {
  for (let name in validators) {
    console.log(`"${s}" - ${validators[name].isValid(s) ? "matches" : "does not match"} ${name}`);
  }
});

Для компиляции этих файлов в один JavaScript‑файл используйте:

Пример


tsc --outFile sample.js main.ts


Пространства имён vs. Модули

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

Понимание, когда использовать пространства имён, а когда — модули, критически важно для разработки на TypeScript:

  • Модули — предпочтительный способ организации кода в современных приложениях TypeScript.
  • Пространства имён по‑прежнему полезны в специфических сценариях (например, для слияния объявлений или работы с унаследованным кодом).
  • Модули имеют лучшую поддержку инструментов и более эффективное удаление неиспользуемого кода.
  • Пространства имён могут быть полезны для создания глобальных библиотек.

Сравнительная таблица

ХарактеристикаПространства имёнES‑модули (import/export)
Рекомендуемая область примененияПростые настройки, небольшие приложения, унаследованные кодовые базыСовременные приложения любого размера; предпочтительно для новых проектов
Синтаксис и использованиеГлобальный доступ через точечную нотацию (например, MyNS.Member)Явный import/export с указанием путей к файлам
Загрузка/сборкаНе требует загрузчика; можно собрать в один файл через --outFileОбычно используется сборщик/загрузчик (Vite, webpack и т. д.)
Разделение по файламВозможно через комментарии /// <reference />Естественно: каждый файл — модуль с явными экспортом и импортом
Удаление неиспользуемого кодаОграничен: сборщикам сложнее исключить неиспользуемый кодОтлично: специально разработан для устранения мёртвого кода
Глобальная область видимостиПоощряет использование глобальных имён (в рамках пространства имён)Избегает глобальных имён; явные зависимости
Расширение/слияниеСильная поддержка через слияние объявленийРасширение модулей возможно, но более ограничено
Инструменты и экосистемаРаботает, но хуже согласуется с современными инструментамиЛучшая поддержка во всех современных инструментах и платформах
Оптимальная сфера примененияУнаследованные библиотеки, глобальные скрипты, типы-декларацииВсе новые разработки, библиотеки и приложения

Расширенные шаблоны пространств имён

Пример


// Исходное пространство имён
declare namespace Express {
  interface Request {
    user?: { id: number; name: string };
  }
  interface Response {
    json(data: any): void;
  }
}

// Позднее в приложении (например, в файле .d.ts)
declare namespace Express {
  // Расширяем интерфейс Request
  interface Request {
    // Добавляем пользовательские свойства
    requestTime?: number;
    // Добавляем методы
    log(message: string): void;
  }

  // Добавляем новые типы
  interface UserSession {
    userId: number;
    expires: Date;
  }
}

// Использование в приложении
const app = express();

app.use((req: Express.Request, res: Express.Response, next) => {
  // Расширенные свойства и методы доступны
  req.requestTime = Date.now();
  req.log('Request started');
  next();
});

Пространства имён с обобщениями (дженериками)

Пример


// Пример пространства имён с обобщениями
namespace DataStorage {
  export interface Repository<T> {
    getAll(): T[];
    getById(id: number): T | undefined;
    add(item: T): void;
    update(id: number, item: T): boolean;
    delete(id: number): boolean;
  }

  // Конкретная реализация
  export class InMemoryRepository<T> implements Repository<T> {
    private items: T[] = [];

    getAll(): T[] {
      return [...this.items];
    }

    getById(id: number): T | undefined {
      return this.items[id];
    }

    add(item: T): void {
      this.items.push(item);
    }

    update(id: number, item: T): boolean {
      if (id >= 0 && id < this.items.length) {
        this.items[id] = item;
        return true;
      }
      return false;
    }

    delete(id: number): boolean {
      if (id >= 0 && id < this.items.length) {
        this.items.splice(id, 1);
        return true;
      }
      return false;
    }
  }
}

// Использование
interface User {
  id: number;
  name: string;
  email: string;
}

const userRepo = new DataStorage.InMemoryRepository<User>();
userRepo.add({ id: 1, name: 'John Doe', email: 'john@example.com' });
const allUsers = userRepo.getAll();


Лучшие практики

Да:

  • Используйте осмысленные, иерархические имена пространств имён.
  • Экспортируйте только то, что действительно нужно из пространств имён.
  • Используйте /// <reference /> для упорядочивания многофайловых пространств имён.
  • Рассмотрите возможность использования модулей для новых проектов.
  • Применяйте const enum внутри пространств имён для повышения производительности.
  • Документируйте пространства имён с помощью комментариев JSDoc.

Нет:

  • Не создавайте чрезмерно глубокие иерархии пространств имён (более 2–3 уровней).
  • Не засоряйте глобальную область видимости без необходимости.
  • Не смешивайте пространства имён и модули в одном проекте без чёткой стратегии.
  • Не используйте пространства имён для небольших приложений — отдавайте предпочтение модулям.

Соображения по производительности

  • Большие пространства имён могут увеличивать размер итоговой сборки.
  • Рассмотрите разделение кода на части для крупных приложений.
  • Учитывайте циклические зависимости в сложных структурах пространств имён.
  • Используйте const enum для повышения производительности при работе с константными значениями.

Миграция с пространств имён на модули

Пример


// До: использование пространств имён
namespace MyApp {
  export namespace Services {
    export class UserService {
      getUser(id: number) { /* ... */ }
    }
  }
}

// После: использование ES‑модулей
// services/UserService.ts
export class UserService {
  getUser(id: number) { /* ... */ }
}

// app.ts
import { UserService } from './services/UserService';
const userService = new UserService();

Шаги миграции

  1. Преобразуйте каждое пространство имён в файл‑модуль.
  2. Замените export на экспорт ES‑модулей.
  3. Обновите импорты, чтобы использовать синтаксис ES‑модулей.
  4. Настройте систему сборки для работы с модулями.
  5. Обновите тесты, чтобы они работали с новой структурой модулей.
  6. Рассмотрите использование сборщика вроде webpack или Rollup.
  7. Обновите tsconfig.json, установив "module": "ESNext".

Инструменты для миграции

  • ts-migrate — автоматизированный инструмент миграции от Facebook.
  • tslint с правилом no-namespace для обнаружения пространств имён.
  • Встроенные инструменты рефакторинга TypeScript.