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

模块

JavaScript 在处理模块化代码方面有着多种不同方式的历史。 自 2012 年以来,TypeScript 已经实现了对其中许多格式的支持,但随着时间的推移,社区和 JavaScript 规范已经统一为一种称为 ES Modules(或 ES6 模块)的格式。你可能知道它就是 import/export 语法。

ES Modules 于 2015 年被添加到 JavaScript 规范中,到 2020 年已在大多数 Web 浏览器和 JavaScript 运行时中得到广泛支持。

为聚焦重点,本手册将涵盖 ES Modules 及其流行的前身 CommonJS module.exports = 语法,关于其他模块模式的信息可在参考部分的模块中找到。

如何定义 JavaScript 模块

在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含顶级 importexport 的文件都被视为一个模块。

相反,没有任何顶级 import 或 export 声明的文件被视为脚本,其内容在全局作用域中可用(因此也对模块可用)。

模块在其自身的作用域内执行,而不是在全局作用域中。 这意味着在模块中声明的变量、函数、类等,除非使用某种导出形式显式导出,否则在模块外部是不可见的。 反之,要使用从另一个模块导出的变量、函数、类、接口等,必须使用某种导入形式将其导入。

非模块

在开始之前,理解 TypeScript 认为什么是模块很重要。 JavaScript 规范声明,任何没有 import 声明、export 或顶级 await 的 JavaScript 文件都应被视为脚本而不是模块。

在脚本文件中,变量和类型被声明为在共享的全局作用域中,并且假定你要么使用 outFile 编译器选项将多个输入文件合并为一个输出文件,要么在 HTML 中使用多个 <script> 标签以正确的顺序加载这些文件!

如果你有一个文件当前没有任何 importexport,但你希望它被视为一个模块,添加以下行:

ts
export {};
Try

这会将文件更改为一个不导出任何东西的模块。无论你的模块目标是什么,此语法都有效。

TypeScript 中的模块

附加阅读:
Impatient JS (模块)
MDN: JavaScript 模块

在 TypeScript 中编写基于模块的代码时,需要考虑三个主要事项:

  • 语法:我想使用什么语法来导入和导出内容?
  • 模块解析:模块名称(或路径)与磁盘上的文件之间是什么关系?
  • 模块输出目标:我生成的 JavaScript 模块应该是什么样子?

ES 模块语法

一个文件可以通过 export default 声明一个主要导出:

ts
// @filename: hello.ts
export default function 
helloWorld
() {
console
.
log
("Hello, world!");
}
Try

然后通过以下方式导入:

ts
import 
helloWorld
from "./hello.js";
helloWorld
();
Try

除了默认导出之外,你还可以通过省略 default 使用 export 导出多个变量和函数:

ts
// @filename: maths.ts
export var 
pi
= 3.14;
export let
squareTwo
= 1.41;
export const
phi
= 1.61;
export class
RandomNumberGenerator
{}
export function
absolute
(
num
: number) {
if (
num
< 0) return
num
* -1;
return
num
;
}
Try

这些可以在另一个文件中通过 import 语法使用:

ts
import { 
pi
,
phi
,
absolute
} from "./maths.js";
console
.
log
(
pi
);
const
absPhi
=
absolute
(
phi
);
Try

其他导入语法

可以使用类似 import {old as new} 的格式重命名导入:

ts
import { 
pi
as
π
} from "./maths.js";
console
.
log
(
π
);
Try

你可以将上述语法混合到单个 import 中:

ts
// @filename: maths.ts
export const 
pi
= 3.14;
export default class
RandomNumberGenerator
{}
// @filename: app.ts import
RandomNumberGenerator
, {
pi
as
π
} from "./maths.js";
RandomNumberGenerator
;
console
.
log
(
π
);
Try

你可以使用 * as name 将所有导出的对象放入一个命名空间中:

ts
// @filename: app.ts
import * as 
math
from "./maths.js";
console
.
log
(
math
.
pi
);
const
positivePhi
=
math
.
absolute
(
math
.
phi
);
Try

你可以通过 import "./file" 导入一个文件,而将任何变量包含到当前模块中:

ts
// @filename: app.ts
import "./maths.js";

console
.
log
("3.14");
Try

在这种情况下,import 什么也不做。然而,maths.ts 中的所有代码都被执行了,这可能会触发影响其他对象的副作用。

TypeScript 特定的 ES 模块语法

类型可以使用与 JavaScript 值相同的语法导出和导入:

ts
// @filename: animal.ts
export type 
Cat
= {
breed
: string;
yearOfBirth
: number };
export interface Dog {
breeds
: string[];
yearOfBirth
: number;
} // @filename: app.ts import {
Cat
, Dog } from "./animal.js";
type
Animals
=
Cat
| Dog;
Try

TypeScript 通过两个概念扩展了 import 语法,用于声明类型的导入:

import type

这是一个只能导入类型的 import 语句:

ts
// @filename: animal.ts
export type 
Cat
= {
breed
: string;
yearOfBirth
: number };
export type
Dog
= {
breeds
: string[];
yearOfBirth
: number };
export const
createCatName
= () => "fluffy";
// @filename: valid.ts import type {
Cat
,
Dog
} from "./animal.js";
export type
Animals
=
Cat
|
Dog
;
// @filename: app.ts import type {
createCatName
} from "./animal.js";
const
name
= createCatName();
'createCatName' cannot be used as a value because it was imported using 'import type'.
Try

内联 type 导入

TypeScript 4.5 还允许为单个导入添加 type 前缀,以指示导入的引用是一个类型:

ts
// @filename: app.ts
import { 
createCatName
, type
Cat
, type
Dog
} from "./animal.js";
export type
Animals
=
Cat
|
Dog
;
const
name
=
createCatName
();
Try

这些特性共同使得非 TypeScript 转译器(如 Babel、swc 或 esbuild)能够知道哪些导入可以安全地移除。

具有 CommonJS 行为的 ES 模块语法

TypeScript 具有与 CommonJS 和 AMD require 直接对应的 ES 模块语法。使用 ES 模块的导入在大多数情况下与这些环境中的 require 相同,但这种语法确保你的 TypeScript 文件与 CommonJS 输出之间具有一一对应的关系:

ts
import 
fs
= require("fs");
const
code
=
fs
.
readFileSync
("hello.ts", "utf8");
Try

你可以在模块参考页面中了解更多关于此语法的信息。

CommonJS 语法

CommonJS 是 npm 上大多数模块交付的格式。即使你使用上面的 ES 模块语法编写,简要了解 CommonJS 语法的工作原理也将有助于你更轻松地调试。

导出

标识符通过设置名为 module 的全局对象上的 exports 属性导出。

ts
function 
absolute
(
num
: number) {
if (
num
< 0) return
num
* -1;
return
num
;
}
module
.
exports
= {
pi
: 3.14,
squareTwo
: 1.41,
phi
: 1.61,
absolute
,
};
Try

然后可以通过 require 语句导入这些文件:

ts
const 
maths
=
require
("./maths");
maths
.
pi
;
Try

或者你可以使用 JavaScript 中的解构特性稍微简化一下:

ts
const { 
squareTwo
} =
require
("./maths");
squareTwo
;
Try

CommonJS 与 ES 模块的互操作

在默认导入和模块命名空间对象导入之间的区别方面,CommonJS 和 ES Modules 存在功能不匹配。TypeScript 有一个编译器标志 esModuleInterop 来减少这两组不同约束之间的摩擦。

TypeScript 的模块解析选项

模块解析是获取 importrequire 语句中的字符串,并确定该字符串引用的文件的过程。

TypeScript 包括两种解析策略:Classic 和 Node。Classic 是当编译器选项 module 不是 commonjs 时的默认策略,为了向后兼容而保留。 Node 策略复制了 Node.js 在 CommonJS 模式下的工作方式,并增加了对 .ts.d.ts 的额外检查。

在 TypeScript 中,有许多 TSConfig 标志会影响模块策略:moduleResolutionbaseUrlpathsrootDirs

有关这些策略如何工作的完整详细信息,请参阅模块解析参考页面。

TypeScript 的模块输出选项

有两个选项会影响生成的 JavaScript 输出:

  • target 决定了哪些 JS 特性会被降级(转换为在旧的 JavaScript 运行时中运行)以及哪些保持不变
  • module 决定了模块之间交互使用什么代码

你使用哪个 target 取决于你期望运行 TypeScript 代码的 JavaScript 运行时中可用的特性。这可能是:你支持的最旧的 Web 浏览器,你期望运行的最低 Node.js 版本,或者可能来自运行时的独特约束——例如 Electron。

模块之间的所有通信都通过模块加载器进行,编译器选项 module 决定了使用哪一个。 在运行时,模块加载器负责在执行模块之前定位并执行其所有依赖项。

例如,这里是一个使用 ES 模块语法的 TypeScript 文件,展示了 module 的几种不同选项:

ts
import { 
valueOfPi
} from "./constants.js";
export const
twoPi
=
valueOfPi
* 2;
Try

ES2020

ts
import { valueOfPi } from "./constants.js";
export const twoPi = valueOfPi * 2;
Try

CommonJS

ts
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;
Try

UMD

ts
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./constants.js"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.twoPi = void 0;
    const constants_js_1 = require("./constants.js");
    exports.twoPi = constants_js_1.valueOfPi * 2;
});
Try

注意,ES2020 实际上与原始 index.ts 相同。

你可以在 module 的 TSConfig 参考中查看所有可用选项及其生成的 JavaScript 代码。

TypeScript 命名空间

TypeScript 有自己的模块格式,称为 namespaces,它早于 ES Modules 标准。这种语法对于创建复杂的定义文件具有许多有用的特性,并且在 DefinitelyTyped 中仍然被积极使用。虽然尚未弃用,但命名空间中的大多数功能在 ES Modules 中都存在,我们建议你使用它来与 JavaScript 的发展方向保持一致。你可以在命名空间参考页面中了解有关命名空间的更多信息。