很多团队第一次认真优化 TypeScript 性能,不是因为喜欢研究编译器,而是因为工作流已经被拖慢了:保存后编辑器长时间转圈,CI 里的 typecheck 越来越久,某个看起来不大的 PR 却能让全仓类型检查多出几分钟。这个阶段如果只把问题归结为“TypeScript 太重”,往往会错过真正有效的优化路径。
类型检查变慢通常有三类原因:项目边界没有拆开、类型表达本身过于昂贵、诊断方式过于粗糙。你如果不先分清是哪一类,后面无论是加缓存、调机器配置,还是试图减少 strict 规则,收益都很有限。真正有效的优化,必须从“先知道时间花在哪”开始。
第一步不是优化,而是测量:别在没有诊断的情况下猜
TypeScript 性能问题最常见的误区,是凭感觉调。某个同事说最近条件类型写多了,另一个同事觉得是编辑器缓存坏了,还有人怀疑是某个新包。没有诊断,这些猜测都很难落地。
先做三件事通常最有价值:
- 用
tsc --extendedDiagnostics看总耗时和主要阶段。 - 用
tsc --explainFiles或项目引用关系,确认是不是全仓都被拉进来了。 - 在可疑项目上用
--generateTrace或编辑器 trace,看哪些文件和类型实例化最重。
这些信息的价值不在于“多几个统计数字”,而在于帮团队把问题从“TypeScript 很慢”拆成更具体的结论:是项目图太大,还是某些类型本身成本过高。
先区分两类慢:图太大,还是类型太贵
| 慢的来源 | 典型表现 | 优先手段 |
|---|---|---|
| 项目图太大 | 改一个包,很多无关项目一起重算 | 项目引用、边界拆分、缓存粒度治理 |
| 类型表达太贵 | 单个文件或少量工具类型就能让编辑器明显卡顿 | 简化条件类型、减少分发式展开、拆解递归类型 |
这两类问题经常同时存在,但优先级不一样。如果全仓所有应用都被拉进同一个检查图,再怎么微调类型技巧,收益都不会太明显;反过来,如果边界已经拆开,但某个工具类型每次都实例化几千次,那项目图再清晰也会被局部热点拖慢。
最容易制造性能热点的,不是“高级类型”这个标签,而是失控的组合
以下几种写法最值得重点怀疑:
- 对大联合类型做分发式条件展开。
- 深层递归条件类型没有明确终止边界。
- 巨大的模板字面量组合类型。
- 把复杂泛型工具直接暴露给全仓高频类型路径。
比如下面这类模式就很容易在规模变大后变贵:
type Normalize<T> = T extends any
? T extends object
? { [K in keyof T]: Normalize<T[K]> }
: T
: never
单看这段类型定义没有问题,问题在于一旦它被应用在很宽的联合、很深的嵌套对象,实例化次数会迅速膨胀。很多性能问题不是来自某一个“错误类型”,而是多个看似合理的组合在一起后成本失控。
边界拆分经常比微调类型更有效
在大型仓库里,最具性价比的优化往往不是先去改最复杂的工具类型,而是先问:为什么这次检查需要把这么多项目一起算?
更稳的优化顺序通常是:
- 用项目引用把独立包拆成可缓存单元。
- 确保应用层不会反向依赖共享包内部实现。
- 让测试、脚本和生产源码分开检查,而不是全塞一处。
- 再回头优化局部高成本类型。
因为只要项目边界还没拆清,任何局部性能优化都可能被更大的依赖图抵消。
一个常见失败案例:为了省导出层,所有类型工具都挂进 shared
某团队把通用条件类型、深只读工具、事件映射、API 响应包装、表单状态工具都集中放到一个共享类型包里。短期看很整齐,长期却造成两个问题:
- 几乎所有项目都会间接依赖这个包,导致它变成性能热点中心。
- 一旦这里某个工具类型变复杂,全仓类型检查都被一起拖慢。
问题不在共享本身,而在于“共享层是否承载了过多高成本类型表达”。如果所有复杂推导都集中在一个被全仓引用的地方,性能问题自然会被放大成系统性问题。
团队治理上,最有价值的是把“类型复杂度预算”说清楚
很多性能问题不是一夜之间出现的,而是团队长期默许“能推出来就行”的结果。比较值得固化的规则包括:
- 高成本工具类型必须配使用边界和示例,不得默认全仓泛用。
- 复杂条件类型优先局部封装,而不是直接暴露为公共 API。
- 编辑器明显卡顿的文件,必须在 review 时追踪具体类型来源。
- 引入新共享类型包时,要评估引用范围和实例化成本,而不是只看复用度。
这类规则的意义不在于限制高级类型,而在于防止高成本类型在没有边界的情况下扩散。
一份类型检查性能排查清单
- 是否先用诊断命令确认耗时主要集中在哪个阶段。
- 项目图是否足够细,还是一次改动就拉全仓参与检查。
- 是否存在高频共享的递归条件类型或超宽联合展开。
- 测试、脚本、生产源码是否被不必要地绑在同一检查上下文里。
- 团队是否有针对高复杂度类型的 review 和约束机制。
总结
TypeScript 性能优化真正有效的路径,不是先怀疑语言,而是先识别:到底是项目图过大,还是类型本身过重。边界拆分解决系统性拖慢,局部类型简化解决热点卡顿,诊断工具负责把两者分开。只要团队愿意把“类型复杂度”和“项目边界”一起纳入治理,TypeScript 在大型仓库里完全可以既严格又可用。
本批次专题导航:
- 工程边界:TypeScript 项目引用与 tsconfig 分层、TypeScript Monorepo 依赖边界治理、TypeScript 类型检查性能优化
- 协议协作:TypeScript 公共 API 设计、TypeScript 运行时校验与静态类型协作、TypeScript 与 OpenAPI 契约协同
- 落地复用:TypeScript 设计模式实战、TypeScript 测试数据构建
- 状态建模:TypeScript 事件系统建模、TypeScript 表单与错误状态建模
本系列导航:
- 如果你还没把项目图拆细,先读 TypeScript 项目引用与 tsconfig 分层
- 若你怀疑慢是依赖图造成的,再看 TypeScript Monorepo 依赖边界治理
- 如果你想把测试层也一起减重,可读 TypeScript 测试数据构建
延伸阅读:


