TypeScript 中的命名空间(Namespaces)详解
命名空间(Namespaces)(以前称为“内部模块”)是 TypeScript 早期用于组织代码、避免全局命名冲突的一种机制。它通过 namespace 关键字将相关类型、函数、类等逻辑分组到一个命名空间内。
注意:在现代 TypeScript 项目中(ES Modules 时代),命名空间已逐渐被 ES6 模块(import/export)取代,官方推荐优先使用模块。但在某些场景(如旧项目、声明文件 .d.ts、或需要声明合并时),命名空间仍有用途。
1. 基本语法与使用
// 定义命名空间
namespace Utils {
export function log(message: string): void {
console.log(`[LOG] ${message}`);
}
export const VERSION = "1.0.0";
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// 不加 export 的成员仅在命名空间内部可见
function internalHelper() {
console.log("内部工具");
}
}
// 使用命名空间
Utils.log("启动应用"); // OK
console.log(Utils.VERSION); // "1.0.0"
let calc = new Utils.Calculator();
console.log(calc.add(5, 3)); // 8
// internalHelper(); // 错误:外部不可访问
关键点:
- 必须使用
export才能从命名空间外部访问成员。 - 命名空间可以嵌套。
2. 嵌套命名空间
namespace App {
export namespace Math {
export function sum(numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
}
export namespace UI {
export class Button {
label: string;
constructor(label: string) {
this.label = label;
}
}
}
}
// 使用
App.Math.sum([1, 2, 3]); // 6
new App.UI.Button("提交");
3. 命名空间别名(Alias)
使用 import 为命名空间起别名,简化长路径访问:
import Calc = App.Math;
Calc.sum([10, 20]); // 30
4. 命名空间与模块的混合使用
命名空间可以与 ES 模块共存(但不推荐混用):
// file: utils.ts
export namespace StringUtils {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
// file: main.ts
import { StringUtils } from "./utils";
StringUtils.capitalize("hello"); // "Hello"
5. 命名空间的声明合并(Declaration Merging)
同名命名空间会自动合并(类似接口合并):
namespace Validation {
export function isEmail(str: string): boolean {
return /\S+@\S+\.\S+/.test(str);
}
}
namespace Validation {
export function isPhone(str: string): boolean {
return /^\d{10,11}$/.test(str);
}
}
// 合并后 Validation 有两个函数
Validation.isEmail("test@example.com"); // true
Validation.isPhone("13800138000"); // true
这在扩展第三方库时非常有用。
6. 命名空间 vs ES 模块(import/export)对比
| 特性 | 命名空间 (namespace) | ES 模块 (import/export) |
|---|---|---|
| 推荐程度 | 旧项目、声明文件 | 现代项目首选 |
| 模块系统 | 基于全局或脚本加载 | 原生 ES Modules,支持 tree-shaking |
| 导出方式 | export 在 namespace 内 | export 在文件顶层 |
| 导入方式 | 点访问(如 Utils.log)或别名 | import { ... } from "./file" |
| 声明合并 | 支持(同名自动合并) | 不支持 |
| 编译输出 | 通常生成全局 IIFE 或 UMD | 生成 ES/CommonJS 模块 |
| 适用场景 | 旧代码、.d.ts 文件、内部组织逻辑 | 所有现代项目(React、Vue、Node.js) |
官方建议:
- 新项目:完全使用 ES 模块(
import/export)。 - 仅在以下情况使用命名空间:
- 维护旧项目(TypeScript < 1.5 时代代码)。
- 编写声明文件(
.d.ts)扩展全局库(如 jQuery)。 - 需要声明合并的特殊场景。
7. 在声明文件中的典型使用(.d.ts)
// jquery.d.ts 示例(简化版)
declare namespace JQuery {
interface JQueryInstance {
html(content: string): this;
on(event: string, handler: Function): this;
}
function $(selector: string): JQueryInstance;
}
declare function $(readyFunc: () => void): void;
// 使用(全局模式)
$("#app").html("Hello").on("click", () => alert("clicked"));
8. 编译选项(tsconfig.json)
使用命名空间时,通常需要配置:
{
"compilerOptions": {
"module": "commonjs", // 或 amd、umd(不推荐 ESNext)
"target": "es5",
"outFile": "./dist/app.js" // 将所有命名空间编译到一个文件(可选)
}
}
现代项目推荐:
{
"module": "ESNext",
"moduleResolution": "node"
}
9. 最佳实践建议
- 新项目避免使用 namespace,改用 ES 模块 + 文件划分。
- 组织代码时优先用文件夹 + import/export。
- 如果必须使用命名空间:
- 保持命名空间小而专注。
- 所有外部成员加
export。 - 使用别名简化访问。
- 迁移旧项目:逐步将 namespace 转为模块。
小结:何时使用命名空间?
| 场景 | 推荐方式 |
|---|---|
| 新项目代码组织 | ES 模块(import/export) |
| 旧项目维护 | 命名空间(逐步迁移) |
| 扩展全局第三方库(.d.ts) | 命名空间 + declare |
| 需要声明合并 | 命名空间或 interface |
结论:在 2025 年的现代 TypeScript 开发中,命名空间已基本被淘汰,除特殊场景外,应始终优先使用 ES6 模块系统。
如果您想看如何将命名空间迁移到模块、声明文件的实际示例,或者其他高级主题(如模块增强、全局类型声明),请告诉我!