- 阐明 :目前网上没有 TypeScript 最新官网文档的中文翻译,所以有了这么一个翻译打算。因为我也是 TypeScript 的初学者,所以无奈保障翻译百分之百精确,若有谬误,欢送评论区指出;
- 翻译内容 :暂定翻译内容为 TypeScript Handbook,后续有空会补充翻译文档的其它局部;
- 我的项目地址 :TypeScript-Doc-Zh,如果对你有帮忙,能够点一个 star ~
本章节官网文档地址:Modules
模块
解决模块化代码的形式很多,JavaScript 在这方面有着悠久的历史。TypeScript 诞生于 2012 年,对许多模块化计划也实现了反对。但随着工夫的推移,社区和 JavaScript 标准在一种名为 ES 模块(或者称为 ES6 模块)的计划上达成了共识。你可能据说过它的 import/export
语法。
ES 模块于 2015 年被纳入 JavaScript 标准,到了 2020 年,它曾经取得了少数 web 浏览器和 JavaScript 运行时的反对。
本手册会重点解说 ES 模块以及在它之前十分风行的、提供了 module.exports =
语法的 CommonJS。在“参考”章节的模块这一大节中,你能够理解到更多对于其它模块化计划的信息。
JavaScript 的模块是如何定义的
和 ECMAScript 2015 一样,TypeScript 会将任何蕴含顶层 import
或者 export
的文件视为一个模块。
反过来,一个不蕴含顶层 import
或者 export
申明的文件会被视为一个脚本,它的内容能够在全局作用域中拜访到(因而对模块也是可见的)。
模块在本身的作用域而非全局作用域中执行。这意味着在一个模块中申明的变量、函数和类等在模块里面是不可见的,除非应用其中一种导出形式将它们显式导出。反过来,为了应用从某个不同的模块中导出的变量、函数、类等,也须要应用其中一种导入形式将它们导入。
非模块
在咱们开始之前,有个很重要的事件须要搞清楚,那就是 TypeScript 会将什么视为一个模块。JavaScript 标准表明,任何不蕴含 export
或者顶层 await
的 JavaScript 文件都应该被视为一个脚本,而不是一个模块。
在一个脚本文件中申明的变量和类型会位于共享的全局作用域中,而且通常状况下,你会应用 outFile 编译选项将多个输出文件合并为一个输入文件,或者应用 HTML 文件中的多个 <script>
标签去(以正确的程序!)加载文件。
如果你的文件以后没有任何的 import
或者 export
,然而你想将其视为一个模块,那么能够增加上面这行代码:
export {};
这会将文件转化为没有导出任何货色的一个模块。不论你的模块指标是什么,这个语法都能够失效。
TypeScript 中的模块
在 TypeScript 中编写基于模块的代码时,有三件次要的事件须要思考:
- 语法: 我想要应用什么语法去进行导入和导出?
- 模块解析: 模块名(或者门路)和磁盘上的文件有什么关系?
- 模块输入指标: 产生的 JavaScript 模块看起来应该是什么样子的?
ES 模块语法
一个文件能够通过 export default
申明一个次要的导出:
// @filename: hello.ts
export default function helloWorld() {console.log("Hello, world!");
}
接着通过上面的语句导入:
import hello from "./hello.js";
hello();
除了默认导出之外,你还能够省略 default
,间接用 export
导出多个变量和函数:
// @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;
}
你能够通过 import
语法在另一个文件中导入:
import {pi, phi, absolute} from "./maths.js";
console.log(pi);
const absPhi = absolute(phi);
^
// const absPhi: number
其它导入语法
能够应用诸如 import {old as new}
这样的模式重新命名一个导入:
import {pi as π} from "./maths.js";
console.log(π);
^
//(alias) var π: number
// import π
你能够将上述语法混合为单个 import
:
// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}
// @filename: app.ts
import RNGen, {pi as π} from "./maths.js";
RNGen;
^
//(alias) class RNGen
//import RNGen
console.log(π);
^
// (alias) const π: 3.14
// import π
应用 * as name
,你能够承受所有导出对象,并将它们放入单个命名空间中:
// @filename: app.ts
import * as math from "./maths.js";
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
^
// const positivePhi: number
应用 import "./file"
,你能够在以后模块中仅导入文件,而不导入文件中的任何变量:
// @filename: app.ts
import "./maths.js";
console.log("3.14");
在这种状况下,import
不会做任何事。不过,math.ts
中的所有代码都会被执行,这可能会触发副作用并影响其它对象。
TypeScript 专属的 ES 模块语法
你能够应用和 JavaScript 值一样的语法将类型进行导出和导入:
// @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;
TypeScript 为 import
语法拓展了两个用处,让它能够申明类型导入:
import type
该导入语句只能导入类型:
// @filename: animal.ts
export type Cat = {breed: string; yearOfBirth: number};
// '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();
内联 type
导入
TypeScript 4.5 也容许单个导入应用 type
前缀表明导入的援用是一个类型:
// @filename: app.ts
import {createCatName, type Cat, type Dog} from "./animal.js";
export type Animals = Cat | Dog;
const name = createCatName();
所有这些都容许诸如 Babel、swc 或者 esbuild 这样的非 TypeScript 转译工具晓得哪些导入是能够被平安地移除的。
具备 CommonJS 行为的 ES 模块语法
TypeScript 的 ES 模块语法能够和 CommonJS 与 AMD 的 require
间接关联。在大多数状况下,应用 ES 模块的导入与雷同环境下应用 require
是一样的,但这个语法能够确保你的 TypeScript 文件和 CommonJS 输入存在一对一的匹配:
import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");
你能够在模块这一参考章节中理解到更多对于该语法的信息。
CommonJS 语法
CommonJS 是大多数 npm 包采纳的模块化计划。即便你编写代码的时候采纳的是 ES 模块语法,简要理解一下 CommonJS 语法的工作形式也有助于简化你的调试过程。
导出
通过给一个名为 module
的全局对象设置 exports
属性,你能够导出标识符:
function absolute(num: number) {if (num < 0) return num * -1;
return num;
}
module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
之后能够通过 require
申明导入这些文件:
const maths = require("maths");
maths.pi;
^
// any
或者你也能够应用 JavaScript 的解构语法只导入一部分内容:
const {squareTwo} = require("maths");
squareTwo;
^
// const squareTwo: any
CommonJS 和 ES 模块的互操作
因为默认导入和模块命名空间对象导入的差别,CommonJS 和 ES 模块在性能上存在不匹配的中央。TypeScript 提供了一个编译选项 esModuleInterop,以缩小这两组不同的束缚之间的抵触。
TypeScript 的模块解析选项
模块解析是一个过程,它指的是从 import
或者 require
申明中提取一个字符串,并确定该字符串所批示的文件。
TypeScript 应用两种解析策略:Classic 和 Node。应用 Classic 策略是为了实现向后兼容,当编译选项 module 不是 commonjs
的时候,默认采纳该策略。Node 策略则复刻了 Node.js 在 CommonJS 模式下的工作形式,并提供了额定的 .ts
和 .d.ts
查看。
还有很多 TSConfig 选项会影响到 TypeScript 中的模块策略,包含:moduleResolution、baseUrl、paths、rootDirs。
无关这些策略如何工作的详细信息,请浏览模块解析。
TypeScript 的模块输入选项
有两个选项会影响到最终输入的 JavaScript:
- target 会决定哪些 JS 个性会被降级(进行转化并运行在比拟旧的 JavaScript 运行时),哪些 JS 个性会被保留
- module 会决定模块之间进行交互所应用的代码
应用哪个 target,取决于你心愿执行 TypeScript 代码的 JavaScript 运行时能够应用的个性。这样的运行时能够是:你反对的最旧的浏览器,你心愿能够运行的最低版本的 Node.js,或者从运行时 —— 比方 Electron 的惟一束缚进行考量。
模块之间的所有通信通过一个模块加载器进行,编译选项 module 会决定应该应用哪一个。在运行时,模块加载器负责在执行模块之前定位并执行模块的所有依赖。
举个例子,这是一个应用 ES 模块语法的 TypeScript 文件:
import {valueOfPi} from "./constants.js";
export const twoPi = valueOfPi * 2;
上面是应用不同的 module 选项之后的编译后果:
ES2020
import {valueOfPi} from "./constants.js";
export const twoPi = valueOfPi * 2;
CommonJS
"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;
UMD
(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;
});
留神:ES2020 实际上和原先的 index.ts 是一样的。
你能够在 TSConfig 这一参考章节的“模块”大节中理解到所有可用的选项以及对应输入的 JavaScript 代码是怎么样的。
TypeScript 命名空间
TypeScript 有本人的模块格局,名为“命名空间”,它比 ES 模块规范呈现得要早。这个语法提供了很多有用的个性以创立简单的定义文件,并且依然广泛应用于 DefinitelyTyped 中。尽管该语法还没有被弃用,但鉴于 ES 模块曾经领有了命名空间的大部分个性,咱们举荐你应用 ES 模块来跟 JavaScript 保持一致。在命名空间的参考章节中,你能够理解到更多相干信息。