编译流程与配置
核心问题解析
问题一:.ts 文件怎么变成 .js?
TypeScript 并不是直接运行的,它需要经过一个“翻译”过程:
example.ts → [tsc 编译] → example.js这个过程由 TypeScript Compiler(tsc) 完成,主要分为三个阶段:
解析(Parsing)
将 .ts 源码字符串解析成 AST(抽象语法树),识别出变量、函数、类型等结构。
绑定(Binding)
构建符号表,建立变量、类型、作用域之间的引用关系,形成语义网络。
类型检查(Checking)
遍历 AST,根据类型规则验证类型安全性(如:string 能否赋给 number)。
代码生成(Emitting)
在类型检查通过后,擦除类型信息,生成纯净的 JavaScript 代码。
类型是“编译时”的存在,运行时完全消失。
示例:
// math.ts
function add(a: number, b: number): number {
return a + b;
}编译后生成:
// math.js
function add(a, b) {
return a + b;
}答案:.ts → AST → 类型检查 → 擦除类型 → .js
问题二:tsconfig.json 到底控制了什么?
tsconfig.json 是 TypeScript 项目的配置中枢,它告诉 tsc:
- 从哪开始编译?
- 编译到哪里?
- 用什么语法标准?
- 是否开启严格检查?
一个典型的 tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"declaration": true
},
"include": ["src"]
}答案:tsconfig.json 控制了编译行为、输出路径、语言标准、类型检查严格度等所有关键环节。
问题三:为什么有些错误不报,有些却报了?
你可能遇到这种情况:
let x = 10;
x = "hello"; // 有时报错,有时不报?这取决于 tsconfig.json 中的配置,尤其是:
- noImplicitAny: 是否允许隐式 any
- strictNullChecks: 是否严格检查 null/undefined
- strict: 是否开启所有严格模式
- 如果 strict: false,TS 会放宽很多检查,导致“看似错误”的代码也能通过。
答案:配置项决定了类型检查的严格程度。TS 不是“不报错”,而是你“没开检查”。
学习目标详解
目标一:理解编译阶段的三个核心步骤
| 阶段 | 作用 | 输出 |
|---|---|---|
| 1. 解析(Parsing) | 将源码转为 AST | 抽象语法树 |
| 2. 绑定(Binding) | 建立符号与作用域关系 | 符号表、语义图 |
| 3. 检查(Checking) | 验证类型安全性 | 错误/警告信息 |
| 4. 生成(Emitting) | 擦除类型,生成 JS | .js、.d.ts、.js.map |
关键点:
- 类型检查发生在生成代码之前
- 即使有错误,默认也会生成 .js(除非 noEmitOnError: true)
目标二:掌握 tsconfig.json 关键配置项
| 配置项 | 作用 | 推荐值 |
|---|---|---|
| target | 编译后的 JS 版本 | "ES2020" 或 "ESNext" |
| module | 模块系统 | "ESNext"(现代项目) |
| outDir | 编译输出目录 | "./dist" |
| rootDir | 源码目录 | "./src" |
| strict | 开启所有严格检查 | true ✅ |
| declaration | 生成 .d.ts 类型声明文件 | true(库项目必备) |
| sourceMap | 生成 source map | true(调试用) |
| esModuleInterop | 兼容 CommonJS 导入 | true |
最佳实践:
- 项目初始化时使用 tsc --init 生成默认配置
- 始终开启 "strict": true
- 明确设置 outDir 和 rootDir 避免输出混乱
- 目标三:理解 --watch 与增量编译机制
- 开发时不想每次手动运行 tsc,可以使用:
tsc --watch它会:
- 监听文件变化
- 只重新编译修改的文件
- 利用编译缓存实现增量编译(Incremental Compilation)
大幅提升开发效率,接近“实时编译”。
原理:
- tsc 会记录文件依赖图
- 修改一个文件后,只重新编译它及其依赖者
- 可通过 --incremental 启用磁盘缓存(.tsbuildinfo 文件)
目标四:熟悉 lib 和 types 配置对全局类型的控制
lib: 指定内置 API 的类型定义
{
"compilerOptions": {
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}- 不写 lib:TS 默认根据 target 自动引入(如 ES5 + DOM)
- 手动指定:精确控制可用的全局对象(如 Promise、console、document)
错误示例:target: ES5 但未引入 ES6 的 lib,则 Promise 会报错。
types: 指定自动引入的类型包
{
"compilerOptions": {
"types": ["node", "@types/react"]
}
}- 控制哪些 @types/* 包会被自动加载
- 默认会加载 node_modules/@types 下的所有包(可能导致命名冲突)
- 显式指定可减少污染
总结:本节核心要点
| 概念 | 关键点 |
|---|---|
| 编译流程 | 解析 → 绑定 → 检查 → 生成(类型被擦除) |
| tsconfig.json | 项目配置的核心,控制一切编译行 |
| strict | 必开!确保类型安全 |
| --watch | 开发利器,支持增量编译 |
| lib | 控制可用的全局 API(如 Promise, Array.from) |
| types | 控制自动引入的第三方类型 |
下一节我们将进入第二阶段,学习如何用接口、泛型等构建类型安全的代码结构。
练习题
练习题 1:补全 tsconfig.json
补全以下 tsconfig.json,要求:
- 源码在 ./src
- 输出到 ./dist
- 使用 ES2021 语法
- 开启严格模式
- 生成类型声明文件
{
"compilerOptions": {
"target": "______",
"module": "ESNext",
"outDir": "______",
"rootDir": "______",
"strict": ______,
"declaration": ______
},
"include": ["______"]
}练习题 2:判断编译结果
有以下代码:
// src/utils.ts
export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));如果 tsconfig.json 中 "target": "ES5","lib": ["ES5"],是否会报错?为什么?如何修复?
练习题 3:理解 --watch 机制
tsc --watch 修改文件后立即重新编译,它是否每次都全量编译?
它是如何提升编译效率的?请简述其背后机制。
练习题 4:lib 配置的影响
以下配置中,哪些全局对象可用?哪些不可用?
{
"compilerOptions": {
"lib": ["ES5"]
}
}选项:Array.from、Promise、console.log、document.getElementById
练习题 5:类型检查开关
假设你有以下代码:
function log(value) {
console.log(value.len);
}在什么配置下这段代码能通过编译?在什么配置下会报错?
如何强制它报错?