Skip to content

TypeScript - 命名空间

命名空间(Namespaces)是 TypeScript 中一种特定的代码组织方式,通过将逻辑相关的功分组来避免污染全局作用域和防止命名冲突,尤其是在大型应用程序中。在标准 ES 模块(import/export)广泛采用之前,它们尤为重要。

建议: 对于大多数现代 TypeScript 项目,ES 模块(import/export)是代码组织和依赖管理的首选标准。命名空间现在不太常见,但仍然存在,并且在特定场景下可能有用。

  • 遗留代码库: 你可能会在较旧的 TypeScript 项目中遇到它们。
  • 组织内部代码: 有时用于大型库或应用程序内部,将内部帮助函数或组件分组,而不将其作为单独的模块暴露。
  • 增强全局对象: 在声明文件(.d.ts)中使用,为现有的全局对象或库添加属性或方法。
  • 简单脚本 / --outFile: 当使用 tsc --outFile 选项将多个 .ts 文件编译成一个单独的 JavaScript 输出文件时(在使用模块系统时较少见)。

命名空间定义以 namespace 关键字开头,后跟命名空间名称。任何打算在命名空间外部使用的内容都必须使用 export 关键字明确导出。

// --- validationUtils.ts ---
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
// 内部细节,未导出
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return emailRegex.test(s);
}
}
export function isString(value: any): value is string {
return typeof value === 'string';
}
}

要从命名空间外部访问导出的成员,使用命名空间名称后跟一个点(.)和成员名称。

// --- app.ts ---
// 假设 validationUtils.ts 已编译/包含
const email = "test@example.com";
const validator: Validation.StringValidator = new Validation.EmailValidator();
if (validator.isValid(email)) {
console.log(`'${email}' 是一个有效的电子邮件地址。`);
} else {
console.log(`'${email}' 不是一个有效的电子邮件地址。`);
}
console.log(`'hello' 是一个字符串吗? ${Validation.isString('hello')}`); // true
// console.log(Validation.emailRegex); // 错误:属性 'emailRegex' 未从命名空间 'Validation' 中导出。

命名空间可以跨越多个文件。TypeScript 将在不同文件中声明的同名命名空间视为合并。为了确保编译器在不使用 ES 模块时了解文件之间的依赖关系,你通常需要:

  • 三斜线引用指令: /// <reference path="./other-file.ts" /> 放在依赖文件的顶部。
  • 编译顺序或 --outFile: 确保文件以正确的顺序编译,或使用 tsc --outFile output.js file1.ts file2.ts ... 打包成一个文件(注意:--outFile 需要 tsconfig.json 中设置 "module": "System" 或 "AMD",并且与 CommonJS、ESNext 等不兼容)。
// --- interfaces.ts ---
namespace MyShapes {
export interface Shape {
getArea(): number;
}
}
// --- circle.ts ---
/// <reference path="interfaces.ts" />
namespace MyShapes {
export class Circle implements Shape {
constructor(public radius: number) {}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
}
// --- rectangle.ts ---
/// <reference path="interfaces.ts" />
namespace MyShapes {
export class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
getArea(): number {
return this.width * this.height;
}
}
}
// --- main.ts ---
/// <reference path="circle.ts" />
/// <reference path="rectangle.ts" />
const circle = new MyShapes.Circle(5);
const rect = new MyShapes.Rectangle(10, 4);
console.log(`圆形面积: ${circle.getArea()}`);
console.log(`矩形面积: ${rect.getArea()}`);
// 使用以下命令编译: tsc --outFile dist/app.js main.ts

同样,这种多文件设置通过 ES 模块可以更优雅地处理,而无需引用指令或 --outFile。

你可以在其他命名空间内部定义命名空间。

namespace App {
export namespace Services {
export class ApiClient {
fetchData() { console.log('正在获取数据...'); }
}
}
export namespace Utils {
export function log(message: string) { console.log(`日志: ${message}`); }
}
}
const client = new App.Services.ApiClient();
client.fetchData();
App.Utils.log("应用程序已启动。");

你可以使用 import alias = Namespace.NestedNamespace; 为嵌套的命名空间创建更短的别名。

import ServiceClient = App.Services.ApiClient;
import logUtil = App.Utils.log;
const client2 = new ServiceClient();
logUtil("正在使用别名。");

注意:这种 import = 语法专门用于导入命名空间和旧的模块格式,与标准的 ES import 关键字不同。

虽然命名空间是早期 TypeScript 的重要组成部分,但 ES 模块(import/export)为现代 JavaScript 和 TypeScript 中的代码组织提供了一种标准化、更强大且更广泛采用的机制。请谨慎使用命名空间,主要是在与遗留系统或特定的声明文件模式交互时。