Menu

Namespace w TypeScript: czym jest i kiedy go używać

Namespace w TypeScript grupuje wartości i typy pod jedną nazwą i kompiluje się do zwykłego obiektu. Poznaj składnię, łączenie namespace'ów ze sobą oraz z funkcjami i klasami, powód, dla którego zastąpiły je ES modules, i miejsca, gdzie wciąż je spotkasz: pliki deklaracji i rozszerzanie globalnego zakresu.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Namespace to nazwany blok, który grupuje funkcje, stałe i typy pod jedną nazwą. Elementy oznaczone export są dostępne jako Name.member; reszta pozostaje prywatna w bloku. W czasie działania namespace jest zwykłym obiektem.

Do czego kompiluje się namespace

Namespace'y to jedna z niewielu funkcji TypeScript, które generują kod. Powyższy blok staje się funkcją, która wypełnia obiekt:

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 = {}));

To wzorzec, którego kod JavaScript używał, zanim pojawiły się moduły, żeby nie umieszczać każdej nazwy w zakresie globalnym. TAU jest zmienną lokalną funkcji, dlatego inny kod jej nie widzi. Próba odczytania jej to błąd kompilacji:

Kompilator zgłasza index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. Dodaj export przed const rate, a obie linie się wykonają.

Zagnieżdżanie, łączenie i aliasy

Namespace'y można zagnieżdżać, a dwa bloki o tej samej nazwie łączą się w jeden. Umożliwia to Geometry || (Geometry = {}) w wyjściu: drugi blok dopisuje do istniejącego obiektu. import X = A.B tworzy krótki alias.

namespace A.B.C { } to skrót od trzech zagnieżdżonych bloków. Dawny zapis module Shop { } znaczy to samo, ale jest teraz odrzucany z błędem TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.

Łączenie z funkcjami i klasami

Namespace może mieć tę samą nazwę co funkcja, klasa lub enum i dodawać do nich elementy. To wciąż najczystszy sposób na opisanie funkcji, która ma też właściwości, albo klasy z dołączonymi funkcjami pomocniczymi.

W przypadku klasy to samo zadanie spełnia metoda static, która jest zwykłym JavaScriptem. W przypadku funkcji możesz też pominąć namespace i przypisać format.prefix = "$" zaraz po deklaracji; TypeScript śledzi właściwości przypisane w ten sposób.

Namespace'y a moduły

Przed ES modules duży program w TypeScript składał się z wielu plików skryptów dzielących globalne namespace'y, połączonych przez /// <reference path="..." /> i kompilowanych do jednego pliku przez outFile. Zastąpiły to moduły: każdy plik ma własny zakres, zależności to jawne importy, a bundlery mogą usuwać nieużywane eksporty. TypeScript 7 usunął outFile (błąd TS5102), więc konfiguracja z namespace'ami w wielu plikach nie jest już opcją budowania.

NamespaceModuł
Jednostkanazwany blok w plikusam plik
Zakresglobalny, chyba że wewnątrz modułuzawsze własny
Zależnościniejawne, przez kolejność wczytywaniajawny import
Wyjścieobiekt budowany przez funkcjęimport/export lub require
Usuwanie nieużywanego kodubundlery zachowują każdy elementbundlery mogą usunąć nieużywane eksporty
Działa przy type stripping w Nodetylko jeśli zawiera same typytak

Wewnątrz modułu opakowanie wszystkiego w namespace dodaje drugi poziom nazw bez żadnego zysku: importujący pisałby Utils.Utils.format. Eksportuj funkcje bezpośrednio i pozwól importującemu wybrać import * as Utils from "./utils.js", jeśli chce przedrostka.

Gdzie wciąż spotkasz namespace'y

Pliki deklaracji używają ich do opisu bibliotek, które udostępniają jeden globalny obiekt, i do grupowania typów:

// 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>;
}

Pakiety typów używają ich do udostępniania typów, które można rozszerzać. @types/node deklaruje namespace NodeJS, a dopisanie czegoś do jego interfejsu ProcessEnv z dowolnego modułu wymaga 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
        }
    }
}

Interfejsy wewnątrz połączonych namespace'ów też się łączą i dzięki temu to rozszerzenie działa.

Namespace'y a type stripping

Node 24 uruchamia pliki .ts, usuwając składnię typów. Namespace z wartościami nie może zostać po prostu usunięty, musi zostać skompilowany do obiektu pokazanego wyżej, więc node app.ts kończy się komunikatem:

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

Dwa rodzaje namespace'ów działają, bo znikają całkowicie: declare namespace oraz namespace, którego wszystkie elementy to typy lub interfejsy. Opcja kompilatora erasableSyntaxOnly: true zgłasza pozostałe już podczas kompilacji jako błąd TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., i tak projekty działające na type stripping trzymają je z daleka. node --experimental-transform-types kompiluje namespace'y, z ostrzeżeniem o funkcji eksperymentalnej.

Najczęściej zadawane pytania

Używać w TypeScript namespace'ów czy modułów?

W nowym kodzie używaj modułów (import i export). Każdy plik ma już własny zakres, bundlery i Node rozumieją moduły, a nieużywane eksporty można usunąć. Namespace'y wciąż przydają się w plikach deklaracji, przy rozszerzaniu globalnego zakresu i do dołączania typów lub funkcji pomocniczych do funkcji albo klasy o tej samej nazwie.

Do czego kompiluje się namespace w TypeScript?

Do obiektu wypełnianego przez natychmiast wywoływaną funkcję: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. Wyeksportowane elementy stają się właściwościami tego obiektu; elementy bez export pozostają lokalne w funkcji.

Czym różni się namespace od modułu w TypeScript?

Moduł to plik z import lub export na najwyższym poziomie. Namespace to nazwany blok wewnątrz pliku. Dawny TypeScript nazywał namespace'y "modułami wewnętrznymi" i pozwalał na module Foo {}; ten zapis jest teraz błędem TS1540 i akceptowane jest tylko namespace Foo {}.

Czy Node uruchomi pliki TypeScript z namespace'ami?

Nie przy domyślnym type stripping. Namespace zawierający wartości daje ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, bo stripping tylko usuwa typy, a namespace wymaga wygenerowanego kodu. Namespace'y zawierające same typy oraz declare namespace są usuwane i działają bez problemu.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ