这篇文章概述了使用 TypeScript 中的模块和命名空间来组织代码的各种方法。我们还将介绍一些关于如何使用命名空间和模块的高级主题,并解决在 TypeScript 中使用它们时的一些常见陷阱。
有关 ES 模块的更多信息,请参阅 模块 文档。有关 TypeScript 命名空间的更多信息,请参阅 命名空间 文档。
注意: 在非常旧版本的 TypeScript 中,命名空间被称为“内部模块”,它们早于 JavaScript 模块系统。
使用模块
模块可以包含代码和声明。
模块还依赖于模块加载器(例如 CommonJs/Require.js)或支持 ES 模块的运行时。模块可以更好地重用代码,加强隔离,并为捆绑提供更好的工具支持。
还值得注意的是,对于 Node.js 应用程序,模块是默认的,并且我们建议在现代代码中使用模块而不是命名空间。
从 ECMAScript 2015 开始,模块成为语言的原生部分,并且应该得到所有符合标准的引擎实现的支持。因此,对于新项目,模块将是推荐的代码组织机制。
使用命名空间
命名空间是 TypeScript 特有的代码组织方式。
命名空间只是全局命名空间中的命名 JavaScript 对象。这使得命名空间成为一个非常简单的构造使用。与模块不同,它们可以跨越多个文件,并且可以使用 outFile
进行连接。命名空间可以是您在 Web 应用程序中构建代码的一种好方法,所有依赖项都包含在您的 HTML 页面中的 <script>
标签中。
就像所有全局命名空间污染一样,很难识别组件依赖关系,尤其是在大型应用程序中。
命名空间和模块的陷阱
在本节中,我们将描述使用命名空间和模块时常见的各种陷阱,以及如何避免它们。
/// <reference>
引用模块
一个常见的错误是尝试使用 /// <reference ... />
语法来引用模块文件,而不是使用 import
语句。为了理解这种区别,我们首先需要了解编译器如何根据 import
的路径(例如 import x from "...";
、import x = require("...");
等中的 ...
)来定位模块的类型信息。
编译器将尝试找到具有适当路径的.ts
、.tsx
,然后是.d.ts
。如果找不到特定文件,则编译器将查找环境模块声明。请记住,这些需要在.d.ts
文件中声明。
-
myModules.d.ts
ts// In a .d.ts file or .ts file that is not a module:declare module "SomeModule" {export function fn(): string;} -
myOtherModule.ts
ts/// <reference path="myModules.d.ts" />import * as m from "SomeModule";
此处的引用标签允许我们找到包含环境模块声明的声明文件。这就是几个 TypeScript 示例使用的node.d.ts
文件被使用的机制。
不必要的命名空间
如果您正在将程序从命名空间转换为模块,您可能会很容易地得到一个看起来像这样的文件
-
shapes.ts
tsexport namespace Shapes {export class Triangle {/* ... */}export class Square {/* ... */}}
此处的顶层命名空间Shapes
毫无理由地包装了Triangle
和Square
。这对您的模块使用者来说令人困惑且令人讨厌
-
shapeConsumer.ts
tsimport * as shapes from "./shapes";let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
TypeScript 中模块的一个关键特性是两个不同的模块永远不会向同一个作用域贡献名称。由于模块的使用者决定要为其分配什么名称,因此无需主动地将导出的符号包装在命名空间中。
为了重申为什么您不应该尝试对模块内容进行命名空间化,命名空间的总体思路是提供构造的逻辑分组并防止名称冲突。由于模块文件本身已经是逻辑分组,并且其顶层名称由导入它的代码定义,因此无需使用额外的模块层来导出对象。
这是一个修改后的示例
-
shapes.ts
tsexport class Triangle {/* ... */}export class Square {/* ... */} -
shapeConsumer.ts
tsimport * as shapes from "./shapes";let t = new shapes.Triangle();
模块的权衡
正如 JS 文件和模块之间存在一对一对应关系一样,TypeScript 在模块源文件及其生成的 JS 文件之间也存在一对一对应关系。这带来的一个影响是,根据您所针对的模块系统,无法将多个模块源文件连接起来。例如,您不能在针对commonjs
或umd
时使用outFile
选项,但是使用 TypeScript 1.8 及更高版本,可以在针对amd
或system
时使用outFile
。