模块
1544字约5分钟
TypeScript
2023-02-01
✨ES模块语法
TypeScript 模块支持所有 ES 模块的语法,特别之处在于允许输出和输入类型。
✨输出类型
type A = "a";
type B = "b";
// 方法一
export { type A, type B };
// 方法二
export type { A, B };
✨引用类型
import
在一条语句中,可以同时输入类型和正常接口。这样很不利于区分类型和正常接口,容易造成混淆。为了解决这个问题,TypeScript 引入了两个解决方法。
(1) 在import
语句输入的类型前面加上type
关键字
import { type A, a } from "./a";
(2) 使用import type
语句,这个语句只能输入类型,不能输入正常接口。
// 正确
import type { A } from "./a";
// 报错
import type { a } from "./a";
// import type 语句也可以输入默认类型。
import type DefaultType from "moduleA";
// import type 在一个名称空间下,输入所有类型的写法如下。
import type * as TypeNS from "moduleA";
✨importsNotUsedAsValues 编译设置
TypeScript 特有的输入类型(type)的 import 语句,编译成 JavaScript 时怎么处理呢?
TypeScript 提供了importsNotUsedAsValues编译设置项,有三个可能的值:
(1)remove:这是默认值,自动删除输入类型的 import 语句。
(2)preserve:保留输入类型的 import 语句。
(3)error:保留输入类型的 import 语句(与preserve相同),但是必须写成import type的形式,否则报错。
✨CommonJS 模块
CommonJS 是 Node.js 的专用模块格式,与 ES 模块格式不兼容。
✨import = 语句
TypeScript 使用import =
语句输入 CommonJS 模块。
import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");
上面示例中,使用import =
语句和require()
命令输入了一个 CommonJS 模块。模块本身的用法跟 Node.js 是一样的。
除了使用import =
语句,TypeScript 还允许使用import * as [接口名] from "模块文件"
输入 CommonJS 模块。
import * as fs from "fs";
// 等同于
import fs = require("fs");
✨export = 语句
TypeScript 使用export =
语句,输出 CommonJS 模块的对象,等同于 CommonJS 的module.exports
对象。
let obj = { foo: 123 };
export = obj;
export =
语句输出的对象,只能使用import =
语句加载。
import obj = require("./a");
console.log(obj.foo); // 123
✨模块定位
模块定位(module resolution)指的是确定 import 语句和 export 语句里面的模块文件位置。
模块定位有两种方法,一种称为 Classic 方法,另一种称为 Node 方法。可以使用编译参数moduleResolution
,指定使用哪一种方法。
没有指定定位方法时,就看原始脚本采用什么模块格式。如果模块格式是 CommonJS(即编译时指定--module commonjs
),那么模块定位采用 Node 方法,否则采用 Classic 方法(模块格式为 es2015、 esnext、amd, system, umd 等等)。
✨相对模块,非相对模块
加载模块时,目标模块分为相对模块(relative import)和非相对模块两种(non-relative import)。
相对模块指的是路径以/、./、../
开头的模块。下面 import 语句加载的模块,都是相对模块。
import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";
非相对模块指的是不带有路径信息的模块。下面 import 语句加载的模块,都是非相对模块。
import * as $ from "jquery";
import { Component } from "@angular/core";
✨Classic 方法
Classic 方法以当前脚本的路径作为“基准路径”,计算相对模块的位置。比如,脚本a.ts
里面有一行代码import { b } from "./b"
,那么 TypeScript 就会在a.ts
所在的目录,查找b.ts
和b.d.ts
。
至于非相对模块,也是以当前脚本的路径作为起点,一层层查找上级目录。比如,脚本a.ts
里面有一行代码import { b } from "b"
,那么就会查找b.ts
和b.d.ts
。
✨Node 方法
Node 方法就是模拟 Node.js 的模块加载方法。
相对模块依然是以当前脚本的路径作为“基准路径”。比如,脚本文件a.ts
里面有一行代码let x = require("./b");
,TypeScript 按照以下顺序查找。
当前目录是否包含
b.ts
、b.tsx
、b.d.ts
。当前目录是否有子目录b,该子目录是否存在文件
package.json
,该文件的types
字段是否指定了入口文件,如果是的就加载该文件。当前目录的子目录b是否包含
index.ts
、index.tsx
、index.d.ts
。
非相对模块则是以当前脚本的路径作为起点,逐级向上层目录查找是否存在子目录node_modules
。比如,脚本文件a.js
有一行let x = require("b");
,TypeScript 按照以下顺序进行查找。
当前目录的子目录
node_modules
是否包含b.ts、b.tsx、b.d.ts。当前目录的子目录
node_modules
,是否存在文件package.json
,该文件的types字段是否指定了入口文件,如果是的就加载该文件。当前目录的子目录
node_modules
里面,是否包含子目录@types
,在该目录中查找文件b.d.ts
。当前目录的子目录
node_modules
里面,是否包含子目录b
,在该目录中查找index.ts
、index.tsx
、index.d.ts
。
进入上一层目录,重复上面 4 步,直到找到为止。
✨路径映射
TypeScript 允许开发者在tsconfig.json
文件里面,手动指定脚本模块的路径。
(1)baseUrl: baseUrl字段可以手动指定脚本模块的基准目录。
{
"compilerOptions": {
"baseUrl": "."
}
}
(2)paths: paths字段指定非相对路径的模块与实际脚本的映射。
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"]
}
}
}
(3)rootDirs: rootDirs字段指定模块定位时必须查找的其他目录。
{
"compilerOptions": {
"rootDirs": ["src/zh", "src/de", "src/#{locale}"]
}
}
✨tsc 的--traceResolution
参数
由于模块定位的过程很复杂,tsc 命令有一个--traceResolution
参数,能够在编译时在命令行显示模块定位的每一步。
tsc --traceResolution
✨tsc 的--noResolve
参数
tsc 命令的--noResolve
参数,表示模块定位时,只考虑在命令行传入的模块。
举例来说,app.ts
包含如下两行代码。
import * as A from "moduleA";
import * as B from "moduleB";
使用下面的命令进行编译。
tsc app.ts moduleA.ts --noResolve
上面命令使用--noResolve
参数,因此可以定位到moduleA.ts
,因为它从命令行传入了;无法定位到moduleB
,因为它没有传入,因此会报错。