Пространства имён в 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();
Шаги миграции
- Преобразуйте каждое пространство имён в файл‑модуль.
- Замените
exportна экспорт ES‑модулей. - Обновите импорты, чтобы использовать синтаксис ES‑модулей.
- Настройте систему сборки для работы с модулями.
- Обновите тесты, чтобы они работали с новой структурой модулей.
- Рассмотрите использование сборщика вроде webpack или Rollup.
- Обновите
tsconfig.json, установив"module": "ESNext".
Инструменты для миграции
ts-migrate— автоматизированный инструмент миграции от Facebook.tslintс правиломno-namespaceдля обнаружения пространств имён.- Встроенные инструменты рефакторинга TypeScript.