4.2 tsconfig 深度配置


4.2 tsconfig 深度配置

这节在地图里是工程化的"总开关房"。第一章只给了最小 tsconfig,这里展开真正影响契约严格度与编译速度的那些项。读懂它们,你才调得动团队的统一口径。

strict 族:七把锁一次上

strict: true 是七个子项的合集。理解每个子项,才能针对老项目精准开启。

{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true } }

各子项作用:

// strictNullChecks:null/undefined 不再随意赋给其它类型 let s: string = null; // 报错(开启后) // strictFunctionTypes:函数参数按逆变检查(见 2.4) type F = (x: Animal) => void; const g: (x: Dog) => void = (x: Animal) => {}; // 开启后才严格 // strictPropertyInitialization:类属性必须初始化 class C { name: string; // 报错:未初始化也未声明可选 constructor() {} // 需 name = "" 或 name?: string }

输入输出:关 strictNullCheckslet s: string = null 通过,开着就拦——这正是"契约要不要管空值"的开关。

noUncheckedIndexedAccess:数组取值也防空

默认 arr[0] 被当作 T 而非 T | undefined,但越界时是 undefined。开启此项让索引访问也变可选。

const arr: number[] = [1]; const x = arr[0]; // 开启后 x: number | undefined if (x !== undefined) x.toFixed(1);

增量编译与缓存

大项目每次全量检查很慢。用 incremental + tsBuildInfoFile 缓存上次的依赖图。

{ "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "composite": true } }

composite 是项目引用的前提(多包仓库),要求开启 incremental。这张图标出开关对"编译速度 vs 严格度"的拉扯:

04-02-fig01-2

排除与包含:控制检查范围

{ "include": ["src"], "exclude": ["node_modules", "dist", "**/*.test.ts"] }

exclude 不会让被 import 的文件逃过检查——只要被 include 内的文件引用,仍会被编译。想彻底跳过某文件用 // @ts-nocheck

项目引用:多包仓库的类型边界

大仓里用 references 让子项目互相看到类型而不必打包。

// 根 tsconfig.json { "files": [], "references": [ { "path": "./packages/core" }, { "path": "./packages/web" } ] }

背景:monorepo 里 web 依赖 core 的类型。操作:core 开 composite,根配 references。结果:web 编译时能拿到 core 的 .d.ts,不用先构建 core。解读:这是类型层面的"软依赖",详见构建集成一节。

skipLibCheck 到底跳过了什么

它只跳过 node_modules.d.ts 的类型检查(第三方库声明),不碰你的源码,也不会让你的代码变宽松——所以它是安全的提速项。

{ "compilerOptions": { "skipLibCheck": true } }

背景:某些老库声明写得不严谨,全量检查会爆一堆与你不相关的错。操作:开 skipLibCheck。结果:检查时间大幅下降,你的 src 仍被完整检查。解读:它不是"关类型检查"的开关,而是"别替别人擦屁股"的开关,和降低严格度是两回事。频繁看到第三方 .d.ts 报错时,第一反应应是它,而非放宽自己。

targetlib 的配合

target 决定编译输出到哪个 ES 版本,lib 决定你能在代码里用哪些内置类型。两者不匹配会"编译报缺定义"。

{ "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM"], "module": "ESNext" } }

背景:写 Array.prototype.at(ES2022)却 target: ES2017,编译会报"at 不存在"。操作:把 lib 提到包含该 API 的版本,或升 target。结果:类型与运行时能力对齐。解读:target 管降级产出、lib 管可用 API 类型,二者要同步;只在浏览器跑的项目加 "DOM" 才能用 document 等类型。

verbatimModuleSyntax:让类型导入显式化

开启后,类型导入必须用 import type,值和类型在语法上彻底分开,配合打包器能更精准树摇。

// 开启 verbatimModuleSyntax 后 import type { User } from "./models"; // 类型:必须加 type import { saveUser } from "./api"; // 值:正常 import // 若把类型写成普通 import 会报错,强制你区分

背景:打包器分不清"这个 import 是值还是类型",可能保留无用代码。操作:开启该开关 + 用 import type。结果:编译产物更干净,类型依赖一眼可辨。解读:这是严格工程化(第四章基调)的进一步收紧,库作者对它收益最大。

工程取舍:迁移期分级开启

老项目别一把 strict: true 砸下去,按这个顺序逐步吃:

# 第一步:只开 null 检查,修一批最危险的错误 # 第二步:开 noImplicitAny # 第三步:开 strictFunctionTypes # 最后:strict: true 收尾

每开一项就修一波,CI 绿了再开下一项。这样错误量可控,团队不崩溃。

实战:lib 与 target 的搭配陷阱

lib 决定哪些内置类型可用(如 PromiseMap),target 决定语法降级到哪一代。两者错配会出诡异报错。

// 场景:target ES5 但代码用了 Promise,且没声明 lib { "compilerOptions": { "target": "ES5" // 未写 lib,则默认按 target 给,ES5 默认不含 Promise 类型 } } // 修正:显式加 lib,或把 target 提到 ES2017+ { "compilerOptions": { "target": "ES5", "lib": ["ES2017", "DOM"] } }

输入输出:缺 libnew Promise(...) 报"找不到名称 Promise";补上 ["ES2017"] 后识别。注意 lib 只补类型,运行时的 Promise 仍需目标环境支持或用 polyfill,类型与运行时要分开看。

实战:verbatimModuleSyntax 与现代隔离

新版本引入 verbatimModuleSyntax,强制区分"值 import"与"类型 import",避免误把类型当值打进产物。

{ "compilerOptions": { "verbatimModuleSyntax": true } }

开启后,凡只用类型的导入必须写 import type,否则报错。它能让打包器的摇树更准,也逼迫团队显式标注类型依赖——代价是老代码要补一堆 import type。解读:这是"类型与值边界"在配置层的强化,配合第四章工程化纪律很合适。

本节要点回顾

  • strict 是七项合集,可逐项精准开启
  • noUncheckedIndexedAccess 让数组取值也防空
  • incremental/skipLibCheck 提速但不降严格度
  • 老项目分级开启,别一次性全开

⚠️ 别用关检查来提速。skipLibCheck 跳过的是第三方声明(通常不归你管),不是你的代码;真正该提速用 incremental,别动严格度。

💡 迁移老项目时,先把 strictNullChecks 单独开起来,它拦下的空值错误往往是最常见也最致命的一类,性价比最高。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U