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 стираются и работают без проблем.