共计 6357 个字符,预计需要花费 16 分钟才能阅读完成。
TypeScript 的官网文档早已更新,但我能找到的中文文档都还停留在比拟老的版本。所以对其中新增以及订正较多的一些章节进行了翻译整顿。
本篇翻译整顿自 TypeScript Handbook 中「Module」章节。
本文并不严格依照原文翻译,对局部内容也做了解释补充。
模块(Module)
JavaScript 有一个很长的解决模块化代码的历史,TypeScript 从 2012 年开始跟进,当初曾经实现反对了很多格局。不过随着工夫流逝,社区和 JavaScript 标准曾经收敛为名为 ES 模块(或者 ES6 模块)的格局,这也就是咱们所晓得的 import/export
语法。
ES 模块在 2015 年被增加到 JavaScript 标准中,到 2020 年,大部分的 web 浏览器和 JavaScript 运行环境都曾经广泛支持。
本章将笼罩解说 ES 模块和和它之前风行的前身 CommonJS module.exports =
语法,你能够在 Modules 章节找到其余的模块模式。
JavaScript 模块是如何被定义的(How JavaScript Modules are Defined)
在 TypeScript 中,就像在 ECMAScript 2015 中,任何蕴含了一个顶层 import
或者 export
的文件会被认为是一个模块。
绝对应的,一个没有顶层导入和导出申明的文件会被认为是一个脚本,它的内容会在全局范畴内可用。
模块会在它本人的作用域,而不是在全局作用域里执行。这意味着,在一个模块中申明的变量、函数、类等,对于模块之外的代码都是不可见的,除非你显示的导出这些值。
绝对应的,要生产一个从另一个的模块导出的值、函数、类、接口等,它也须要应用导入的格局先被导入。
非模块(Non-modules)
在咱们开始之前,咱们须要先了解 TypeScript 认为什么是一个模块。JavaScript 标准申明任何没有 export
或者顶层 await
的 JavaScript 文件都应该被认为是一个脚本,而非一个模块。
在一个脚本文件中,变量和类型会被申明在共享的全局作用域,它会被假设你或者应用 outFile 编译选项,将多个输出文件合并成一个输入文件,或者在 HTML 应用多个 <script>
标签加载这些文件。
如果你有一个文件,当初没有任何 import
或者 export
,然而你心愿它被作为模块解决,增加这行代码:
export {};
这会把文件改成一个没有导出任何内容的模块,这个语法能够失效,无论你的模块指标是什么。
TypeScript 中的模块(Modules in TypeScript)
在 TypeScript 中,当写一个基于模块的代码时,有三个次要的事件须要思考:
- 语法:我想导出或者导入该用什么语法?
- 模块解析:模块名字(或门路)和硬盘文件之间的关系是什么样的?
- 模块导出指标:导出的 JavaScript 模块长什么样?
ES 模块语法(ES Module Syntax)
一个文件能够通过 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
附加导入语法(Additional Import Syntax)
一个导入也能够应用相似于 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
的所有代码都会执行,触发一些影响其余对象的副作用(side-effects)。
TypeScript 具体的 ES 模块语法(TypeScript Specific ES Module Syntax)
类型能够像 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();
内置类型导入(Inline type imports)
TypeScript 4.5 也容许独自的导入,你须要应用 type
前缀,表明被导入的是一个类型:
// @filename: app.ts
import {createCatName, type Cat, type Dog} from "./animal.js";
export type Animals = Cat | Dog;
const name = createCatName();
这些能够运行一个非 TypeScript 编译器比方 Babel、swc 或者 esbuild 晓得什么样的导入能够被平安移除。
导入类型和内置类型导入的区别在于一个是导入语法,一个是仅仅导入类型
有 CommonJS 行为的 ES 模块语法(ES Module Syntax with CommonJS Behavior)
TypeScript 的 ES 模块语法跟 CommonJS 和 AMD required
间接相干。应用 ES 模块的导入跟 require
一样都适宜大部分的状况。然而这个语法能确保你在有 CommonJS 输入的 TypeScript 文件里,有一个 1 对 1 的匹配:
import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");
你能够在模块援用页面理解到对于这个语法更多的信息。
CommonJS 语法(CommonJS Syntax)
CommonJS 是 npm 大部分模块的格局。即便你正在写 ES 模块语法,理解一下 CommonJS 语法的工作原理也会帮忙你调试更容易。
导出(Exporting)
通过设置全局 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 and ES Modules interop)
因为默认导出和模块申明空间对象导出的差别,CommonJS 和 ES 模块不是很适合一起应用。TypeScript 有一个 esModuleInterop 编译选项能够缩小两种束缚之间的抵触。
TypeScript 模块解析选项(TypeScript’s Module Resolution Options)
模块解析是从 import
或者 require
语句中取出字符串,而后决定字符指向的是哪个文件的过程。
TypeScript 蕴含两个解析策略:Classic 和 Node。Classic,当编译选项 module 不是 commonjs
时的默认抉择,蕴含了向后兼容。Node 策略则复制了 CommonJS 模式下 Nodejs 的运行形式,会对 .ts
和 .d.ts
有额定的查看。
这里有很多 TSConfig 标记能够影响 TypeScript 的模块策略:
moduleResolution, baseUrl, paths, rootDirs。
对于这些策略工作的残缺细节,你能够参考 Module Resolution。
TypeScript 模块输入选项(TypeScript’s Module Output Options)
有两个选项能够影响 JavaScript 输入的文件:
- target 决定了哪个 JS 个性会被降级(被转换成能够在更老的 JavaScript 运行环境应用),哪些则残缺保留。
- module 决定了模块里的那些代码会被用于和彼此互动
你应用哪个 target 取决于你冀望代码运行的环境。这些能够是:你须要反对的最老的浏览器,你冀望代码运行的最老的 Nodejs 版本,或者一些独特的运行环境束缚,比方 Electron 等。
所有模块之间的交换都是通过模块加载器,编译选项 module 决定了哪一种会被用到。在运行时,模块加载器会在执行它之前定位和执行一个模块所有的依赖。
举个例子,这是一个应用 ES Module 语法的 TypeScript 文件,展现了 module 选项不同导致的后果:
import {valueOfPi} from "./constants.js";
export const twoPi = valueOfPi * 2;
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 namespaces)
TypeScript 有它本人的模块格局,名为 namespaces
。它在 ES 模块规范之前呈现。这个语法有一系列的个性,能够用来创立简单的定义文件,当初仍然能够在 DefinitelyTyped 看到。当没有被废除的时候,命名空间次要的个性都还存在于 ES 模块,咱们举荐你对齐 JavaScript 方向应用。你能够在命名空间页面理解更多。
TypeScript 系列
- TypeScript 之 根底入门
- TypeScript 之 常见类型(上)
- TypeScript 之 常见类型(下)
- TypeScript 之 类型收窄
- TypeScript 之 函数
- TypeScript 之 对象类型
- TypeScript 之 泛型
- TypeScript 之 Keyof 操作符
- TypeScript 之 Typeof 操作符
- TypeScript 之 索引拜访类型
- TypeScript 之 条件类型
- TypeScript 之 映射类型
- TypeScript 之模板字面量类型
- TypeScript 之类(上)
- TypeScript 之类(下)
微信:「mqyqingfeng」,加我进冴羽惟一的读者群。
如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。