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