Namespace là một khối có tên, gom các hàm, hằng số và kiểu dưới một tên. Các thành viên được đánh dấu export có thể truy cập qua Name.member; phần còn lại là riêng tư trong khối. Lúc chạy, namespace là một đối tượng thường.
Namespace được biên dịch thành gì
Namespace là một trong số ít tính năng TypeScript sinh ra code. Khối ở trên trở thành một hàm điền dữ liệu vào một đối tượng:
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 = {}));
Đây là mẫu mà code JavaScript dùng trước khi có module, để tránh đặt mọi tên vào phạm vi toàn cục. TAU là biến cục bộ của hàm, nên code khác không nhìn thấy nó. Cố đọc nó sẽ gặp lỗi biên dịch:
Trình biên dịch báo index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. Thêm export trước const rate thì cả hai dòng đều chạy.
Lồng nhau, hợp nhất và bí danh
Namespace có thể lồng nhau, và hai khối cùng tên sẽ hợp nhất thành một. Phần Geometry || (Geometry = {}) trong đầu ra là thứ làm điều đó khả thi: khối thứ hai thêm vào đối tượng đã có. import X = A.B tạo một bí danh ngắn.
namespace A.B.C { } là cách viết tắt cho ba khối lồng nhau. Cách viết cũ module Shop { } có cùng ý nghĩa nhưng giờ bị từ chối với lỗi TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.
Hợp nhất với hàm và lớp
Namespace có thể dùng chung tên với một hàm, lớp hoặc enum và thêm thành viên cho nó. Đây vẫn là cách gọn nhất để mô tả một hàm đồng thời mang thuộc tính, hoặc một lớp có gắn kèm các hàm hỗ trợ.
Với lớp, phương thức static làm cùng việc đó và là JavaScript thuần. Với hàm, bạn cũng có thể bỏ namespace và gán format.prefix = "$" ngay sau khai báo; TypeScript theo dõi các thuộc tính được gán theo cách đó.
Namespace và module
Trước khi có ES module, một chương trình TypeScript lớn gồm nhiều tệp script dùng chung các namespace toàn cục, ghép lại bằng /// <reference path="..." /> và biên dịch thành một tệp với outFile. Module đã thay thế cách làm đó: mỗi tệp có phạm vi riêng, phụ thuộc là các câu lệnh import rõ ràng, và bundler có thể bỏ các export không dùng tới. TypeScript 7 đã bỏ outFile (lỗi TS5102), nên cách tổ chức namespace nhiều tệp không còn là một lựa chọn build.
| Namespace | Module | |
|---|---|---|
| Đơn vị | một khối có tên trong tệp | chính tệp đó |
| Phạm vi | toàn cục, trừ khi nằm trong một module | luôn riêng |
| Phụ thuộc | ngầm định, theo thứ tự nạp | import rõ ràng |
| Đầu ra | một đối tượng do một hàm tạo ra | import/export hoặc require |
| Loại bỏ code không dùng | bundler giữ mọi thành viên | bundler có thể bỏ export không dùng |
| Chạy được với type stripping của Node | chỉ khi nó chỉ chứa kiểu | có |
Bên trong một module, bọc mọi thứ trong một namespace chỉ thêm một tầng tên mà không được lợi gì: nơi import sẽ phải viết Utils.Utils.format. Hãy export trực tiếp các hàm và để nơi import tự chọn import * as Utils from "./utils.js" nếu họ muốn có tiền tố.
Những nơi bạn vẫn gặp namespace
Tệp khai báo dùng chúng để mô tả các thư viện chỉ lộ ra một đối tượng toàn cục, và để gom nhóm kiểu:
// 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>;
}
Các package kiểu dùng chúng để lộ ra những kiểu mà bạn có thể mở rộng. @types/node khai báo namespace NodeJS, và để thêm vào interface ProcessEnv của nó từ bất kỳ module nào, bạn cần 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
}
}
}
Interface bên trong các namespace được hợp nhất cũng hợp nhất theo, và đó là lý do việc mở rộng này hoạt động.
Namespace và type stripping
Node 24 chạy tệp .ts bằng cách xóa cú pháp kiểu. Một namespace chứa giá trị không thể xóa đi được, nó phải được biên dịch thành đối tượng như ở trên, nên node app.ts dừng lại với:
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode
Có hai loại namespace vẫn ổn vì chúng biến mất hoàn toàn: declare namespace, và namespace mà mọi thành viên đều là kiểu hoặc interface. Tùy chọn trình biên dịch erasableSyntaxOnly: true báo những loại còn lại ngay lúc biên dịch với lỗi TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., và đó là cách các dự án chạy bằng type stripping loại chúng ra. node --experimental-transform-types có biên dịch namespace, kèm một cảnh báo thử nghiệm.
Câu hỏi thường gặp
Nên dùng namespace hay module trong TypeScript?
Hãy dùng module (import và export) cho code mới. Mỗi tệp vốn đã có phạm vi riêng, bundler và Node đều hiểu module, và các export không dùng tới có thể bị loại bỏ. Namespace vẫn hữu ích trong tệp khai báo, cho global augmentation, và để gắn kiểu hoặc hàm hỗ trợ vào một hàm hay lớp cùng tên.
Namespace trong TypeScript được biên dịch thành gì?
Một đối tượng được điền bởi một hàm gọi ngay lập tức: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. Các thành viên được export trở thành thuộc tính của đối tượng đó; thành viên không có export vẫn là biến cục bộ của hàm.
namespace và module trong TypeScript khác nhau thế nào?
Module là một tệp có import hoặc export ở cấp cao nhất. Namespace là một khối có tên bên trong một tệp. TypeScript thời trước gọi namespace là "internal module" và cho phép module Foo {}; cách viết đó giờ là lỗi TS1540, và chỉ namespace Foo {} được chấp nhận.
Node có chạy được tệp TypeScript dùng namespace không?
Không, với type stripping mặc định. Một namespace chứa giá trị sẽ gây ra ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, vì type stripping chỉ xóa kiểu, trong khi namespace cần code được sinh ra. Namespace chỉ chứa kiểu, và declare namespace, bị xóa hoàn toàn nên vẫn chạy bình thường.