TypeScript支持从零搭建到项目落地的完整实践指南

爱学习的春景 组件 阅读 882
赞 59 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

上周上线一个带类型提示的组件库文档站,本地 dev server 启动要 5.2 秒,热更新(HMR)平均延迟 3.8 秒,改一行 interface 就得等 4 秒才看到效果。最离谱的是 VS Code 的 TypeScript Server 每次自动重载后,整个编辑器光标卡顿、智能提示失灵,我甚至怀疑自己是不是开了什么全家桶插件——关了所有插件,问题照旧。

TypeScript支持从零搭建到项目落地的完整实践指南

不是项目小,是它真小:就 12 个组件,TypeScript 声明文件加起来不到 800 行。但 node_modules 里 @types/react + typescript + ts-jest + vue-tsc(我们混用了 Vue 和 React 组件)硬生生把 TS 服务拖进泥潭。跑 tsc --noEmit --watch 直接 CPU 占满,风扇起飞。

找到痛点了!

先用 tsc --traceResolution 看了一眼模块解析路径,发现每次改动都触发全量重新检查——哪怕只改了一个 .d.ts 文件,TS 都会从 node_modules/@types/ 往上翻所有依赖树。又试了 typescript --explainFiles,输出里赫然列出 1700+ 个被包含的文件,其中 1264 个来自 @types/react 和它的“亲戚” @types/react-dom@types/react-router……但我们的组件库根本不用 react-router,只用 React.FC 和几个 hooks。

再开 VS Code 的 TS Server 日志(Developer: Toggle Developer Tools → Console),搜 Project loading,发现每次保存都触发 updateGraphWorker,耗时稳定在 3200ms±200ms。定位很清晰:不是代码写得烂,是 TS 在做太多它本不必做的事。

试了几种方案

  • @types/react-router?删了,没用。因为 @types/react-dom 依赖 @types/react,而后者又通过 typesVersions 主动引入一堆 polyfill 类型,删不干净。
  • skipLibCheck: true?跳过声明文件检查,确实快了——启动降到 1.4 秒。但代价是类型安全裸奔,React.ReactNode 写成 React.NodeReact 都不报错,上线前 CI 里 tsc --noEmit 一跑直接挂,放弃。
  • 拆成多个 tsconfig.json?搞了个 tsconfig.build.json 专用于构建,和 tsconfig.dev.json 分离。有用,但没根治。VS Code 默认读根目录 tsconfig.json,除非你手动告诉它用哪个——太反人类,团队新人一来就懵。

折腾了半天发现:真正的病灶不在依赖多,而在 TS 默认把所有 node_modules/@types/* 全部纳入程序图(program graph)。而我们只需要确保组件导出的类型能被正确消费,不需要让 TS 去“理解”整个 React 生态的类型定义。

最后这个效果最好:用 types 字段精准收口

核心思路:不让 TS 自动扫描 node_modules/@types,而是显式声明“我只信任这几个类型包”。改 tsconfig.json

{
  "compilerOptions": {
    "types": ["react", "react-dom"],
    "typeRoots": ["./node_modules/@types"]
  }
}

注意:删掉 lib 里冗余项,只留刚需。我们组件库是纯类型声明 + Markdown 文档渲染,根本不用 DOM API 或 ES2022 新语法,所以:

{
  "compilerOptions": {
    "lib": ["ES2020", "DOM"],
    "types": ["react", "react-dom"]
  }
}

别写 ["ES2023", "DOM", "WebWorker", "ScriptHost"] 这种全家桶,每多一个 lib,TS 就得多加载几百个内置声明文件。

更关键的是——禁用自动类型获取。在 tsconfig.json 里加这行:

{
  "compilerOptions": {
    "types": ["react", "react-dom"],
    "typeRoots": ["./node_modules/@types"],
    "allowSyntheticDefaultImports": false,
    "skipDefaultLibCheck": true
  }
}

这里 skipDefaultLibCheck 是重点:它跳过对 lib.dom.d.ts 等默认库的完整性校验,但不跳过类型检查本身。实测下来,既保住了 HTMLElement 这类基础类型可用,又砍掉了 90% 的无意义校验开销。

还有个小技巧:把组件库自己的类型声明(比如 index.d.ts)放到单独目录,然后在 tsconfig.json 里用 include 精确控制:

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

别用 "include": ["**/*"],那是自找死路。

优化后:流畅多了

改完立刻测:

  • VS Code TS Server 重载时间:从 3200ms → 420ms(降幅 87%)
  • dev server 启动:5.2s → 840ms
  • HMR 响应:3.8s → 790ms(改一个 interface,0.8 秒内提示更新完成)
  • CPU 占用峰值:100% → 22%

顺手跑了一遍 tsc --noEmit --watch,内存占用从 1.4GB 降到 360MB。现在边敲代码边喝咖啡,风扇安静得我以为它坏了。

当然,不是完美。比如某个同事非要在组件里写 import { createBrowserRouter } from 'react-router-dom',那 TS 还是会去拉它的类型——但这是业务代码问题,不是配置问题。我们文档站本来就不该 import 路由,CI 里加个 ESLint 规则 no-restricted-imports 拦住就行。

性能数据对比

下面是三次典型操作的耗时记录(单位:ms,取 5 次平均值):

操作 优化前 优化后 降幅
TS Server 初始化 3210 425 86.8%
dev server 启动 5230 845 83.8%
HMR(改 .ts 文件) 3790 785 79.3%
tsc --noEmit 单次检查 2940 610 79.2%

所有数据都是本地 M1 Mac Mini(16GB)实测。没有魔法,就是让 TS 少干点事。

踩坑提醒:这三点一定注意

  • 不要乱设 typeRoots。我第一次写成 "typeRoots": ["./types", "./node_modules/@types"],结果 TS 优先查 ./types,导致自己写的 index.d.ts 里漏了个 export,编译不报错但下游消费时报 Cannot find module——查了两小时才发现是路径顺序惹的祸。建议只写 ["./node_modules/@types"],其他类型通过 types 字段引入。
  • skipDefaultLibCheck 不等于 skipLibCheck。前者只跳过默认库(如 lib.dom.d.ts)的校验,后者跳过全部第三方类型。我们选前者,不然 React.FC 的类型推导就崩了。
  • Vue + React 混用项目务必确认 @vue/runtime-core@types/react 的版本兼容性。我们踩过一次:升级 typescript@5.3 后,@types/react@18.2 里的 JSX.IntrinsicAttributes 和 Vue 的类型冲突,报一堆 Type instantiation is excessively deep。降级到 @types/react@18.0.37 解决。这类问题没法自动化,只能靠 yarn why @types/react 锁版本。

以上是我的优化经验,有更好的方案欢迎交流

这个方案不是银弹——如果你的组件库要支持 SSR、要生成 d.ts、要对接 Storybook 的类型推导,可能还得加 declaration: trueemitDeclarationOnly,那构建时间又会涨回来一点。但对绝大多数内部文档站、设计系统站点来说,够用了。

我也试过 tsc --incremental + tsbuildinfo,但增量构建在频繁修改类型定义时反而容易失效,不如直接砍掉冗余类型来得干脆。

如果你也在用 typescript@5.x + 多框架混写 + VS Code,欢迎评论区甩出你的 tsconfig.json 片段,一起看看还能怎么压榨。或者,你有更狠的骚操作?求分享。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论