参考阅读:https://blog.51cto.com/u_13028258/5754309
简介
任何包含 import 或 export 语句的文件,就是一个模块(module)。相应地,如果文件不包含 export 语句,就是一个全局的脚本文件。模块本身就是一个作用域,不属于全局作用域。模块内部的变量、函数、类只在内部可见,对于模块外部是不可见的。暴露给外部的接口,必须用 export 命令声明;如果其他文件要使用模块的接口,必须用 import 命令来输入
上面这行语句不产生任何实际作用,但会让当前文件被当作模块处理,所有它的代码都变成了内部代码。TypeScript 模块除了支持所有 ES 模块的语法,特别之处在于允许输出和输入类型
上面示例中,当前脚本输出一个类型别名 Bool。这行语句把类型定义和接口输出写在一行,也可以写成两行
假定上面的模块文件为 a.ts,另一个文件 b.ts 就可以使用 import 语句,输入这个类型
上面示例中,import 语句加载的是一个类型。注意,加载文件写成 ./a,没有写脚本文件的后缀名。TypeScript 允许加载模块时,省略模块文件的后缀名,它会自动定位,将 ./a 定位到 ./a.ts。编译时,可以两个脚本同时编译
import type 语句
import 在一条语句中,可以同时输入类型和正常接口
这样很不利于区分类型和正常接口,容易造成混淆。为了解决这个问题,TypeScript 引入了两个解决方法
- 第一个方法是在 import 语句输入的类型前面加上 type 关键字
- 第二个方法是使用 import type 语句,这个语句只能输入类型,不能输入正常接口
import type 语句也可以输入默认类型
import type 在一个名称空间下,输入所有类型的写法如下
同样的,export 语句也有两种方法,表示输出的是类型
- 示例中,方法一是使用 type 关键字作为前缀,表示输出的是类型
- 方法二是使用 export type 语句,表示整行输出的都是类型
上面示例中,由于使用了 export type 语句,输出的并不是 Point 这个类,而是 Point 代表的实例类型。输入时,只能作为类型输入
importsNotUsedAsValues 编译设置
TypeScript 特有的输入类型(type)的 import 语句,编译成 JavaScript 时怎么处理呢 ?TypeScript 提供了 importsNotUsedAsValues 编译设置项,有三个可能的值
- remove:这是默认值,自动删除输入类型的 import 语句
- preserve:保留输入类型的 import 语句
- error:保留输入类型的 import 语句(与preserve相同),但是必须写成 import type 的形式,否则报错
CommonJS 模块
CommonJS 是 Node.js 的专用模块格式,与 ES 模块格式不兼容
import = 语句
TypeScript 使用 import = 语句输入 CommonJS 模块。示例中,使用 import = 语句和 require() 命令输入了一个 CommonJS 模块。模块本身的用法跟 Node.js 是一样的
除了使用 import = 语句,TypeScript 还允许使用 import * as [接口名] from “模块文件” 输入 CommonJS 模块
export = 语句
TypeScript 使用 export = 语句,输出 CommonJS 模块的对象,等同于 CommonJS 的 module.exports 对象
export = 语句输出的对象,只能使用 import = 语句加载
模块定位
概述
TypeScript 怎么确定 ./a 或 jquery 到底是指哪一个模块,具体位置在哪里,用到的算法就叫做“模块定位”
编译参数 moduleResolution,用来指定具体使用哪一种定位算法。常用的算法有两种:Classic 和 Node
- 如果没有指定 moduleResolution,它的默认值与编译参数 module 有关
- module 设为 commonjs 时(项目脚本采用 CommonJS 模块格式)
- moduleResolution 的默认值为 Node,即采用 Node.js 的模块定位算法
相对模块|非相对模块
下面 import 语句加载的模块,都是相对模块。相对模块的定位,是根据当前脚本的位置进行计算的,一般用于保存在当前项目目录结构中的模块脚本
- import Entry from “./components/Entry”;
- import { DefaultHeaders } from “../constants/http”;
- import “/mod”;
下面 import 语句加载的模块,都是非相对模块。非相对模块的定位,是由 baseUrl 属性或模块映射而确定的,通常用于加载外部模块
- 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 的模块加载方法,也就是 require() 的实现方法
- 相对模块依然是以当前脚本的路径作为“基准路径”。比如,脚本文件 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 文件里面,手动指定脚本模块的路径
- baseUrl
- paths
- rootDirs
tsc 的 —traceResolution 参数
由于模块定位的过程很复杂,tsc 命令有一个 —traceResolution 参数,能够在编译时在命令行显示模块定位的每一步。示例中,traceResolution 会输出模块定位的判断过程
tsc 的 —noResolve 参数
tsc 命令的 —noResolve 参数,表示模块定位时,只考虑在命令行传入的模块。举例来说,app.ts 包含如下两行代码
使用下面的命令进行编译
上面命令使用 —noResolve 参数,因此可以定位到 moduleA.ts,因为它从命令行传入了;无法定位到 moduleB,因为它没有传入,因此会报错