模块

JavaScript 在处理代码模块化方面有着悠久的历史,并采用了不同的方法。TypeScript 自 2012 年问世以来,已经实现了对许多这些格式的支持,但随着时间的推移,社区和 JavaScript 规范已经收敛到一种名为 ES 模块(或 ES6 模块)的格式。您可能将其称为 import/export 语法。

ES 模块于 2015 年被添加到 JavaScript 规范中,到 2020 年,大多数 Web 浏览器和 JavaScript 运行时都广泛支持它。

为了集中精力,手册将涵盖 ES 模块及其流行的前身 CommonJS module.exports = 语法,您可以在参考部分的 模块 中找到有关其他模块模式的信息。

JavaScript 模块的定义方式

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

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

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

非模块

在我们开始之前,了解 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

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

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);
const absPhi: number
Try

其他导入语法

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

ts
import { pi as π } from "./maths.js";
 
console.log(π);
(alias) var π: number import π
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;
(alias) class RandomNumberGenerator import RandomNumberGenerator
 
console.log(π);
(alias) const π: 3.14 import π
Try

可以使用 * as name 将所有导出的对象放入单个命名空间。

ts
// @filename: app.ts
import * as math from "./maths.js";
 
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
const positivePhi: number
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

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

ts
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
'createCatName' cannot be used as a value because it was imported using 'import type'.1361'createCatName' cannot be used as a value because it was imported using 'import type'.
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();
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

这些功能共同使 Babel、swc 或 esbuild 等非 TypeScript 编译器能够识别哪些导入可以安全删除。

ES 模块语法与 CommonJS 行为

TypeScript 具有 ES 模块语法,它与 CommonJS 和 AMD 的 `require` 直接相关。使用 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;
any
Try

或者,你可以使用 JavaScript 中的解构功能简化代码

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

CommonJS 和 ES 模块互操作性

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

TypeScript 的模块解析选项

模块解析是指将 importrequire 语句中的字符串转换为该字符串所指文件的过程。

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

许多 TSConfig 标志会影响 TypeScript 中的模块策略: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 模块标准。这种语法在创建复杂的定义文件方面有很多有用的功能,并且在 DefinitelyTyped 中仍然被积极使用。虽然没有被弃用,但命名空间中的大多数功能都存在于 ES 模块中,我们建议您使用它来与 JavaScript 的方向保持一致。您可以在 命名空间参考页面 中了解更多关于命名空间的信息。

TypeScript 文档是一个开源项目。帮助我们改进这些页面 通过发送 Pull Request

此页面的贡献者
RCRyan Cavanaugh (52)
OTOrta Therox (7)
MMyo (3)
ABAndrew Branch (2)
HAHossein Ahmadian-Yazdi (2)
18+

上次更新:2024 年 3 月 21 日