【Typescript】13-tsconfig与工程化实践 tsconfig与工程化实践很多人学 TypeScript 时会把注意力几乎全部放在语法上泛型会不会写、infer看不看得懂、工具类型会不会用。可真正在工程里决定 TypeScript 上限的往往不是这些而是tsconfig.json。因为它决定了编译器到底有多严格、如何解析模块、怎样理解你的项目边界以及你愿不愿意在开发期就把风险暴露出来。说得直接一点很多团队 TypeScript 用得一般不是因为不会语法而是因为配置太松。tsconfig.json本质上是在定义团队和编译器之间的契约它不是一个“复制粘贴模板文件”而是在回答这些问题我们允许多宽松的类型行为我们的代码会被编译到什么运行环境我们的模块如何解析我们是否把类型检查和构建拆开哪些文件参与编译哪些不参与所以tsconfig本质上体现的是团队对类型安全的态度而不仅仅是工具链配置。一个实用的起点配置{compilerOptions:{target:ES2020,module:ESNext,moduleResolution:Bundler,strict:true,noUncheckedIndexedAccess:true,exactOptionalPropertyTypes:true,skipLibCheck:true}}这不是放之四海而皆准的唯一答案但它代表一种相对成熟的取向尽量让问题在开发期暴露而不是把模糊和侥幸留到运行时。strict是真正开启 TypeScript 的开关如果只记一个配置项那应该是strict。开启strict后TypeScript 会开始认真处理隐式any空值问题不安全赋值宽松的函数参数兼容各类潜在类型漏洞不开strict你当然还是“在用 TypeScript”但很多最有价值的保护其实都被你自己关掉了。如果你在接手老项目不一定能一夜之间把严格选项全开因为历史包袱可能很重。但长期方向应该清晰逐步收紧而不是为了清静持续放松。noUncheckedIndexedAccess能暴露很多被忽略的真实风险看一个例子constmap:Recordstring,number{};constvaluemap[x];不开noUncheckedIndexedAccess时value可能会被推成number开启后它会变成number | undefined。后者更符合现实因为你根本不能保证x一定存在。这个选项非常值得强调因为大量线上 bug 都来自类似思维我以为这个 key 一定有我以为这个数组下标一定取得到我以为这个映射表已经初始化了而 TypeScript 如果不够严格就会默许这些“我以为”。exactOptionalPropertyTypes让可选属性语义更准确很多人对可选属性的理解过于粗糙以为它只是“可以不写”。但从业务上看这通常至少有两层不同含义属性不存在属性存在但值是undefined开启exactOptionalPropertyTypes后TypeScript 会更认真地区分这两种情况。这对这些场景尤其有价值PATCH 更新接口表单回填与提交配置覆盖逻辑DTO 与领域对象转换你会开始更明确地思考这个字段是真的可缺失还是只是值可能为空。skipLibCheck为什么很多项目会开skipLibCheck: true的含义是跳过对依赖库声明文件的完整类型检查。很多项目会打开它原因通常是提升编译性能避免被第三方库声明问题卡住这在工程上是个务实选择但你要知道它的代价你对某些依赖声明问题的感知会下降。所以它适合做性能与稳定性的折中不适合被理解成“这样更安全”。target、module、moduleResolution不只是语法项它们和工具链强相关target决定输出代码面向哪个 JavaScript 运行环境比如ES2017、ES2020。module决定模块输出形式例如ESNext、CommonJS。moduleResolution决定 TypeScript 如何解析模块路径和依赖现代前端项目中常见Bundler或NodeNext。这些配置不是单独存在的它们通常要和你的构建工具、运行环境、测试工具一起考虑。也就是说tsconfig不是纯 TypeScript 世界里的孤岛而是整个工程工具链的一部分。类型检查和打包构建是两回事这是现代前端和 Node.js 项目里非常关键的一层认知。很多人以为运行tsc就等于项目构建但在现在的工具链里事情常常不是这样。例如Vite 可能负责打包Next.js 可能负责编译和路由构建esbuild 或 SWC 可能负责转译tsc --noEmit只负责类型检查所以你需要明确区分谁负责类型检查谁负责代码转译谁负责产物构建否则遇到问题时很容易把责任归错地方。一个成熟项目的 TypeScript 原则真正成熟的 TypeScript 项目通常会坚持几个原则对外暴露的 API 类型明确核心领域对象类型稳定any控制在极少数必要场景对外部不可信数据先做运行时校验再进入类型系统利用工具类型减少重复配置保持尽可能严格其中最容易被忽略的一条是运行时校验。因为 TypeScript 再强也只存在于编译阶段。一个接口只要返回了脏数据单靠类型注解并不会自动拯救你。运行时校验为什么必须和 TypeScript 配套看很多团队学到后面会有一种错觉类型已经写得这么完整了系统应该很安全。实际上这只对“你自己写出来的静态结构”成立对外部世界并不成立。典型风险包括后端返回结构和定义不一致本地存储中的 JSON 被污染URL 参数缺失或格式不对第三方库返回非预期数据这时像 Zod、Valibot 这类运行时校验库就很重要。它们不是和 TypeScript 重复而是在补足 TypeScript 无法覆盖的那一半现实世界。老项目怎么逐步变严格如果你现在面对的是一个历史包袱很重的项目不必一口气把所有配置拉满。更务实的方式是先开启strict清理最危险的隐式any逐步引入更精确的空值和索引访问检查对新增模块要求更高对存量模块渐进改造类型治理本身就是工程治理不可能永远靠一次性重写完成。本文小结TypeScript 的工程质量不只由你会不会写语法决定更由配置和团队边界决定。tsconfig.json不是一个“项目启动时顺手拷来的配置文件”它实际上定义了整个代码库对风险的容忍度和对类型安全的投资力度。你可以把它理解成一种工程立场到底是愿意在开发期多面对一些报错还是愿意把更多不确定性留到运行时。成熟团队通常会选择前者。练习新建一个tsconfig.json手动解释target、module、strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes的含义。找一个老项目看看是否开启了strict并评估如果要逐步收紧配置第一步最适合做什么。思考哪些运行时错误仅靠 TypeScript 不能解决你会如何用运行时校验工具补上这一部分。后记2026年5月22日于上海。