阮一峰《TypeScript 教程》学习笔记——模块
1. 一段话总结
TypeScript 模块是指包含 import 或 export 语句的文件(无 export 需加export {}强制为模块),模块内部成员默认仅内部可见,对外暴露需用export,外部使用需用import;TS 特支持类型导入导出(如import type仅导入类型、export type仅导出类型),并通过importsNotUsedAsValues编译选项控制类型导入语句的编译行为;同时兼容 CommonJS 模块,用import = require()导入、export =导出;模块定位分Classic 方法(以当前脚本路径为基准)和Node 方法(模拟 Node.js 加载逻辑),支持通过baseUrl/paths/rootDirs进行路径映射;编译时可通过--traceResolution查看定位过程、--noResolve限制仅处理命令行传入模块。
2. 思维导图

3. 详细总结
一、模块基础
-
模块定义
- 任何包含
import或export语句的文件即为模块;无export但需作为模块(内部成员私有)时,需添加空导出语句:export {}(无实际作用,仅标记为模块)。 - 模块拥有独立作用域,内部变量、函数、类默认仅内部可见,对外暴露需用
export,外部使用需用import。
- 任何包含
-
TS 模块核心特性
- 支持类型的导入与导出(区别于 JS 仅导出值),示例:
// 导出类型 export type Bool = true | false; // 导入类型 import { Bool } from './a'; let foo:Bool = true; - 模块导入可省略后缀名(TS 自动定位
.ts/.d.ts文件,如import { A } from './a'定位到./a.ts)。
- 支持类型的导入与导出(区别于 JS 仅导出值),示例:
-
模块编译
- 多文件编译:
tsc a.ts b.ts(分别生成a.js/b.js);仅编译入口文件时,TS 自动编译依赖(如tsc b.ts自动编译其依赖的a.ts)。 - 合并编译:用
--outFile参数将多文件合并为一个(如tsc --outFile result.js b.ts)。
- 多文件编译:
二、类型导入与导出(TS 特有)
为区分“类型”与“值”的导入导出,TS 提供专属语法,避免混淆:
| 操作类型 | 语法示例 | 说明 |
|---|---|---|
| 导入类型(方式1) | import { type A, a } from './a' | type A 标记导入的是类型,a 是值 |
| 导入类型(方式2) | import type { A } from './a' | 仅导入类型,导入值会报错 |
| 导出类型(方式1) | export { type A, type B } | type 标记导出的是类型 |
| 导出类型(方式2) | export type { A, B } | 整行仅导出类型(如导出类的实例类型) |
- importsNotUsedAsValues 编译选项
控制“仅导入类型”的import语句在编译后的处理方式,有3个取值:
| 选项值 | 编译行为 | 示例(编译前:import { TypeA } from './a') |
|---|---|---|
| remove | 默认值,删除该 import 语句 | 编译后:无此语句 |
| preserve | 保留语句(仅触发模块副作用,不导入内容) | 编译后:import './a'; |
| error | 保留语句,但强制用 import type,否则报错 | 需改为 import type { TypeA } from './a' |
三、CommonJS 模块支持(兼容 Node.js)
TS 提供专属语法兼容 CommonJS 模块(与 ES 模块不兼容):
-
导入 CommonJS 模块
- 语法1:
import 模块名 = require('模块路径')(推荐,明确 CommonJS 导入); - 语法2:
import * as 模块名 from '模块路径'(等效于语法1)。
示例:
import fs = require('fs'); // 导入 Node.js 的 fs 模块 const code = fs.readFileSync('hello.ts', 'utf8'); - 语法1:
-
导出 CommonJS 模块
- 语法:
export = 导出对象(等效于 CommonJS 的module.exports); - 限制:仅能通过
import = require()导入,无法用普通import。
示例:
// 导出(a.ts) let obj = { foo: 123 }; export = obj; // 导入(b.ts) import obj = require('./a'); console.log(obj.foo); // 123 - 语法:
四、模块定位(确定模块文件位置)
模块定位指 TS 如何根据 import 路径找到实际模块文件,分“模块分类”“定位方法”“路径映射”三部分:
-
模块分类
分类 路径特征 示例 相对模块 以 //.//../开头./components/Entry、../constants/http非相对模块 无路径信息 jquery、@angular/core -
两种定位方法
通过moduleResolution编译选项指定,默认规则:模块格式为 CommonJS 时用 Node 方法,否则用 Classic 方法。定位方法 核心逻辑 查找步骤(以导入 b为例)Classic 以当前脚本路径为基准,逐层向上查找 1. 当前目录查 b.ts/b.d.ts;2. 上级目录重复查找Node 模拟 Node.js 加载逻辑,优先查 node_modules1. 当前目录 node_modules查b.ts/b.d.ts;2. 查package.json的types字段;3. 查@types/b.d.ts;4. 查b/index.ts;5. 上级目录重复1-4 -
路径映射(tsconfig.json 配置)
手动指定模块路径,解决复杂目录结构下的定位问题,需配合baseUrl使用:配置字段 作用 示例配置 baseUrl 指定模块定位的基准目录(必填,如 .)"baseUrl": "."paths 非相对模块与实际路径的映射(数组可多路径) "jquery": ["node_modules/jquery/dist/jquery"]rootDirs 指定额外需查找的目录(如国际化目录) "rootDirs": ["src/zh", "src/de"]
五、编译参数(辅助模块定位)
-
–traceResolution
编译时输出模块定位的每一步详细过程,用于排查定位失败问题,命令:tsc --traceResolution。 -
–noResolve
仅处理命令行传入的模块,不自动查找依赖模块。示例:# 仅处理 app.ts 和 moduleA.ts,moduleB 未传入会报错 tsc app.ts moduleA.ts --noResolve
4. 关键问题
问题1:TypeScript 中 import type 与普通 import 的核心区别是什么?分别适用于哪些场景?
答案:
核心区别在于导入内容的类型与编译行为:
- 导入内容:
import type仅能导入“类型”(如接口、类型别名、类的实例类型),导入“值”(如变量、函数、类本身)会报错;普通import可同时导入类型和值。 - 编译行为:
import type导入的类型语句,会根据importsNotUsedAsValues选项处理(如remove模式下会删除);普通import导入值的语句会保留,触发模块副作用(如执行模块内代码)。
适用场景:
import type:仅需使用模块中的类型,不依赖模块副作用(如仅用接口定义变量类型);- 普通
import:需同时使用模块的类型和值(如既用fs模块的类型,又调用fs.readFileSync方法)。
问题2:模块定位的 Classic 方法与 Node 方法在查找逻辑上的核心差异是什么?实际项目中如何选择?
答案:
核心差异体现在查找基准与非相对模块处理:
- 查找基准:两者对相对模块的查找基准一致(当前脚本路径),但非相对模块的查找逻辑完全不同:
- Classic 方法:以当前脚本路径为起点,逐层向上查找模块文件(如
import 'b'查当前目录→上级目录的b.ts); - Node 方法:模拟 Node.js 逻辑,优先在各级
node_modules目录中查找(如查当前目录node_modules/b.ts→上级目录node_modules/b.ts),且会处理package.json的types字段、@types目录等。
- Classic 方法:以当前脚本路径为起点,逐层向上查找模块文件(如
选择原则:
- 若项目为浏览器环境(非 CommonJS 模块)(如 ES 模块、AMD 模块),选择 Classic 方法;
- 若项目为Node.js 环境(CommonJS 模块),选择 Node 方法(默认),确保与 Node.js 运行时的模块加载逻辑一致,避免定位偏差。
问题3:paths 路径映射的配置逻辑是什么?如何通过 baseUrl 与 paths 配合,解决“长路径导入”问题?请举例说明。
答案:
paths 路径映射的核心逻辑是:以 baseUrl 为基准目录,将“非相对模块名”映射到实际文件路径,支持多路径 fallback(数组第一个路径不存在时查第二个)。
配置步骤与示例(解决长路径问题):
假设项目结构如下:
src/components/Button/index.ts
tsconfig.json
若不配置 paths,导入 Button 需写长路径:import Button from './src/components/Button',配置后可简化为 import Button from '@components/Button',具体配置:
- 配置
baseUrl为项目根目录(tsconfig.json所在目录):"compilerOptions": {"baseUrl": "." } - 配置
paths,将@components/*映射到src/components/*:"compilerOptions": {"baseUrl": ".","paths": {"@components/*": ["src/components/*"] // * 为通配符,匹配任意子路径} } - 使用简化导入:
import Button from '@components/Button'; // 等效于 './src/components/Button'
原理:TS 会将 @components/Button 按 paths 映射为 src/components/Button,再以 baseUrl(.)为基准,定位到 ./src/components/Button/index.ts。
