Menu

Namespace в TypeScript: что это и когда использовать

Namespace в TypeScript объединяет значения и типы под одним именем и компилируется в обычный объект. Синтаксис, слияние пространств имён друг с другом, с функциями и классами, почему их заменили ES-модули и где они встречаются до сих пор: файлы объявлений и глобальное расширение.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Namespace это именованный блок, который объединяет функции, константы и типы под одним именем. Члены с пометкой export доступны как Name.member; остальные остаются приватными для блока. Во время выполнения namespace это обычный объект.

Во что компилируется namespace

Namespace одна из немногих возможностей TypeScript, которые генерируют код. Блок выше превращается в функцию, которая заполняет объект:

var Geometry;
(function (Geometry) {
    const TAU = Math.PI * 2;
    function circumference(radius) {
        return TAU * radius;
    }
    Geometry.circumference = circumference;
    function area(radius) {
        return Math.PI * radius ** 2;
    }
    Geometry.area = area;
})(Geometry || (Geometry = {}));

Этим приёмом код на JavaScript пользовался до появления модулей, чтобы не складывать все имена в глобальную область видимости. TAU это локальная переменная функции, поэтому другой код её не видит. Попытка её прочитать даёт ошибку компиляции:

Компилятор сообщает index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. Добавьте export перед const rate, и обе строки выполнятся.

Вложенность, слияние и псевдонимы

Namespace могут быть вложенными, а два блока с одинаковым именем сливаются в один. Именно Geometry || (Geometry = {}) в сгенерированном коде это обеспечивает: второй блок дополняет уже существующий объект. import X = A.B создаёт короткий псевдоним.

namespace A.B.C { } это сокращение для трёх вложенных блоков. Старая запись module Shop { } означает то же самое, но теперь отвергается с ошибкой TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.

Слияние с функциями и классами

Namespace может иметь то же имя, что функция, класс или enum, и добавлять к ним члены. Это по-прежнему самый чистый способ описать функцию, у которой есть свойства, или класс с прикреплёнными вспомогательными функциями.

Для класса ту же задачу решает static-метод, и это обычный JavaScript. Для функции можно обойтись без namespace и присвоить format.prefix = "$" сразу после объявления; TypeScript отслеживает свойства, присвоенные таким образом.

Namespace и модули

До ES-модулей большая программа на TypeScript состояла из множества скриптов с общими глобальными пространствами имён, связанных через /// <reference path="..." /> и скомпилированных в один файл с помощью outFile. Модули это заменили: каждый файл это своя область видимости, зависимости это явные импорты, а бандлеры могут выбрасывать неиспользуемые экспорты. В TypeScript 7 outFile удалён (ошибка TS5102), так что многофайловая схема на namespace больше не вариант сборки.

NamespaceМодуль
Единицаименованный блок в файлесам файл
Область видимостиглобальная, если не внутри модулявсегда своя
Зависимостинеявные, по порядку загрузкиявный import
Результатобъект, построенный функциейimport/export или require
Удаление неиспользуемого кодабандлеры оставляют все членыбандлеры могут выбросить неиспользуемые экспорты
Работает при удалении типов в Nodeтолько если содержит одни типыда

Внутри модуля оборачивать всё в namespace значит добавлять второй уровень имён без всякой пользы: импортирующему коду пришлось бы писать Utils.Utils.format. Экспортируйте функции напрямую, и пусть импортирующий код сам выберет import * as Utils from "./utils.js", если ему нужен префикс.

Где namespace встречаются до сих пор

Файлы объявлений используют их, чтобы описывать библиотеки, которые открывают один глобальный объект, и чтобы группировать типы:

// jquery-like.d.ts: a global function that also has properties
declare function $(selector: string): unknown;
declare namespace $ {
    const version: string;
    function ajax(url: string): Promise<unknown>;
}

Пакеты типов используют их, чтобы открыть типы, которые можно расширять. @types/node объявляет namespace NodeJS, и чтобы дополнить его интерфейс ProcessEnv из любого модуля, нужен declare global:

// env.d.ts
export {};

declare global {
    namespace NodeJS {
        interface ProcessEnv {
            API_URL: string; // process.env.API_URL is now string, not string | undefined
        }
    }
}

Интерфейсы внутри слитых пространств имён тоже сливаются, и именно поэтому такое расширение работает.

Namespace и удаление типов

Node 24 запускает файлы .ts, удаляя синтаксис типов. Namespace со значениями удалить нельзя, его нужно скомпилировать в объект, показанный выше, поэтому node app.ts останавливается с ошибкой:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode

Два вида namespace работают без проблем, потому что исчезают полностью: declare namespace и namespace, все члены которого это типы или интерфейсы. Опция компилятора erasableSyntaxOnly: true сообщает об остальных при компиляции ошибкой TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., и так проекты, работающие на удалении типов, не пускают их в код. node --experimental-transform-types компилирует namespace, но с предупреждением об экспериментальной функции.

Часто задаваемые вопросы

Что использовать в TypeScript: namespace или модули?

Для нового кода используйте модули (import и export). Каждый файл уже является своей областью видимости, бандлеры и Node понимают модули, а неиспользуемые экспорты можно удалить. Namespace остаются полезны в файлах объявлений, для глобального расширения и чтобы прикрепить типы или вспомогательные функции к функции или классу с тем же именем.

Во что компилируется namespace в TypeScript?

В объект, который заполняет немедленно вызываемая функция: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. Экспортированные члены становятся свойствами этого объекта; члены без export остаются локальными для функции.

Чем namespace отличается от module в TypeScript?

Модуль это файл с import или export на верхнем уровне. Namespace это именованный блок внутри файла. Старый TypeScript называл пространства имён «внутренними модулями» и разрешал module Foo {}; теперь такая запись даёт ошибку TS1540, и принимается только namespace Foo {}.

Может ли Node запускать файлы TypeScript с namespace?

Со стандартным удалением типов нет. Namespace, содержащий значения, даёт ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, потому что удаление типов только стирает типы, а namespace требует сгенерированного кода. Namespace, содержащие только типы, и declare namespace стираются и работают без проблем.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ