命名空间(Namespace)是代码组织的机制,它在 pre-ES6 时代就出现了,用于将逻辑上相关的实体进行组合。它帮助结构化大型项目,将类、函数、接口和类型聚合在一个命名空间内,以避免名称冲突,并使代码更易读。
问题背景:在 JavaScript ES6 标准出现之前,开发者使用 IIFE、对象和命名空间来模拟模块化。TypeScript 引入了关键字 namespace (之前称为 internal module)来组织代码。
问题:现代模块(ES6 Module)已成为标准,因此命名空间和模块这两种方法并存,这在架构设计上造成了混淆——什么时候使用命名空间,什么时候使用模块更好?
解决方案:命名空间在纯 TypeScript 项目中仍然有助于将辅助函数和类型结合在一起(例如,通过输出 outFile 生成单个 JS 文件)。在不同文件之间分隔代码时,特别是在与 npm 和现代打包器一起工作时,使用模块更合适。命名空间常用于内部库、类型声明以及在一个文件或旧代码中需要结构化的情况。
代码示例:
namespace MyMath { export function add(a: number, b: number) { return a + b; } } console.log(MyMath.add(2, 3)); // 5
关键特征:
outFile 选项时使用,通常在现代项目中很少使用命名空间如果将不同文件中的命名空间合并,会是一个命名空间还是多个?
TypeScript 执行 声明合并 — 如果名称相同,不同部分的命名空间合并成一个,只要它们正确定义并位于同一作用域内。
// mathA.ts namespace MathUtil { export function sum(a: number, b: number) { return a + b; } } // mathB.ts namespace MathUtil { export function mul(a: number, b: number) { return a * b; } } // 编译后,MathUtil 包含了两个方法
可以像模块一样使用 import/require 来导入命名空间吗?
不可以,命名空间没有默认导出/命名导出,无法使用 ES6 的标准语法进行导入。命名空间是纯 TypeScript 概念,不会被转译为 JavaScript 模块。
可以通过 import 从命名空间导入值到另一个文件吗?
不可以,从其他文件访问命名空间时,需要使用引用(/// <reference path="..." />)或使用 outFile 编译,无法通过 import 进行导入。
在一个旧项目中,将逻辑分解为数十个命名空间,相同的名称出现在多个文件中,导致意外合并或冲突。在转向模块化架构时,代码迁移变得非常困难。
优点:
缺点:
在一个大型库中通过在一个文件 index.d.ts 中声明命名空间来定义 API,统一了所有类型和接口而不涉及代码实现。这使得快速为库的消费者提供类型变得更加容易,并且简单地更新团队之间的合同。
优点:
缺点: