Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

命名空间与模块

本文概述了在 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.ts

    ts
    // 在 .d.ts 文件或不是模块的 .ts 文件中:
    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

    ts
    export namespace Shapes {
      export class Triangle {
        /* ... */
      }
      export class Square {
        /* ... */
      }
    }

这里的顶级命名空间 Shapes 无缘无故地包装了 TriangleSquare。 这对于模块的使用者来说既令人困惑又烦人:

  • shapeConsumer.ts

    ts
    import * as shapes from "./shapes";
    let t = new shapes.Shapes.Triangle(); // shapes.Shapes?

TypeScript 中模块的一个关键特性是,两个不同的模块永远不会将名称贡献到同一个作用域。 因为模块的使用者决定为其分配什么名称,所以没有必要主动将导出的符号包装在命名空间中。

再次强调为什么你不应该尝试将模块内容命名空间化,命名空间的一般思想是提供构造的逻辑分组并防止名称冲突。 因为模块文件本身已经是一个逻辑分组,并且其顶级名称由导入它的代码定义,所以没有必要为导出的对象使用额外的模块层。

下面是一个修改后的示例:

  • shapes.ts

    ts
    export class Triangle {
      /* ... */
    }
    export class Square {
      /* ... */
    }
  • shapeConsumer.ts

    ts
    import * as shapes from "./shapes";
    let t = new shapes.Triangle();

模块的权衡

正如 JS 文件和模块之间存在一对一的关系一样,TypeScript 在模块源文件和它们生成的 JS 文件之间也存在一对一的关系。 其影响之一是,根据你定位的模块系统,不可能将多个模块源文件连接起来。 例如,当目标为 commonjsumd 时,你不能使用 outFile 选项,但从 TypeScript 1.8 开始,当目标为 amdsystem 时,可以 使用 outFile