游乐游手机版
首页/前端开发/文章详情

老项目引入TypeScript意外提升全公司代码审查标准

时间:2026-07-18 06:42
一位前端工程师为老项目悄悄引入TypeScript,从API层补类型定义并开启strict模式。同事效仿后,全组逐步迁移至TS,代码审查标准随之抬高:类型定义成标配,禁止使用any,PR通过率下降但线上bug减少,重构更安全。

先说一个真实案例。去年第四季度,有位前端开发人员在修改表单提交功能时,遇到了一个棘手的问题。接口返回的数据结构大致如下:

给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了

TypeScript

 复制代码// 后端实际返回的
{
  userInfo: {
    name: "张三",
    // 有时候有,有时候没有
    // 取决于用户有没有填
  }
}

他在前端代码里直接写了 userInfo.phone.slice(0, 3),结果线上环境报出了 Cannot read properties of undefined 的错误。排查了半小时才发现,是一位老用户没有填写手机号。

类似的问题在这个项目中已经不是第一次出现了。这是一套祖传代码,基于 Vue2 和 JavaScript 构建,前后经历了 5 拨开发人员,变量命名风格从驼峰式到下划线再到拼音,五花八门。每次迭代需求都像在拆盲盒——你永远无法预判哪个属性是 undefined,哪个函数返回的是 Promise 而你却忘了加 await

那天晚上他失眠了。不是因为那个 bug,而是因为他意识到:这种代码他已经改不动了。

二、决策:悄悄引入 TS,只改造自己负责的模块

他曾向组长提过两次全量迁移 TypeScript 的方案,但都被驳回了。理由很充分:工期紧张、风险过高、其他同事还不熟悉 TS。

于是他决定:不向上请示,直接开干。但只聚焦自己的一亩三分地。

他挑选了一个相对独立的业务模块——用户中心。这个模块代码量不大,但业务逻辑复杂,接口调用频繁,正好适合验证 TypeScript 的实际价值。

第一步:渐进式引入

他没有进行全量改写,而是在 tsconfig.json 中开启了 allowJs: true,让 JavaScript 和 TypeScript 能够共存。新文件使用 .ts 后缀,老文件保持原样不动。

JSON

 复制代码// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ESNext",
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noImplicitAny": false
  }
}

注意,他关闭了 strictnoImplicitAny。这是有意的策略——先让 TypeScript 跑起来,避免一开始就陷入大量 any 类型标注的困境。

第二步:从 API 层入手

他首先补全了所有接口响应的类型定义。这一步虽然最枯燥,但带来的收益也是最大的。

TypeScript

 复制代码// types/api.ts
interface UserInfo {
  name: string;
  phone?: string;  // 可选,因为可能没填
  email?: string;
  a vatar?: string;
  createdAt: string;
}interface ApiResponse<T> {
  code: number;
  data: T;
  message?: string;
}

接着为 axios 的响应添加了泛型支持:

TypeScript

 复制代码// utils/request.ts
import axios from 'axios';const request = axios.create({
  baseURL: '/api',
  timeout: 10000,
});// 关键:给响应加类型
export function getUserInfo(): Promise<ApiResponse<UserInfo>> {
  return request.get('/user/info');
}

第三步:顺势开启 strict 模式

运行了两天后,他发现 strict: false 的情况下,TypeScript 几乎拦截不住多少潜在问题。比如他仍然可以写出 const a = null; a.toString() 而不会收到任何报错。

咬了咬牙,他开启了 strict: true。瞬间屏幕上爆出了 200 多个错误。他花了两个周末的时间逐个修复,大部分改动是将 let 替换为 const,补充了一些 ?. 可选链操作符,还发现了几个真正的逻辑漏洞。

三、意外:同事们开始来"抄作业"了

他将这个模块的代码提交后,组长在 review 时愣了一下。组长问道:"你这代码怎么突然变长了?"

他解释了一下 TypeScript 带来的好处。组长没再多说什么,直接 merge 了。

一周后,隔壁组的同事私信他:"你那个 UserInfo 的类型定义能借我用一下吗?我们也在对接用户中心的接口。"

两周后,另一位同事跑来问他:"你那个 request.ts 里的泛型是怎么写的?我也想给接口加上类型。"

一个月后,组长在组会上宣布:"以后新模块统一使用 TypeScript 吧,老模块逐步迁移。"

他当时的内心活动:???我本来只是想让自己爽一下啊。

四、后果:代码审查标准被抬高了

事情从这里开始逐渐失控。

第一个月:类型定义成了标配

以前代码审查,大家关注的是"功能对不对""有没有内存泄漏"。现在审查的第一句话变成了:"这个参数的类型定义在哪里?"

TypeScript

 复制代码// 以前这样写,完全没问题
function formatDate(date) {
  return dayjs(date).format('YYYY-MM-DD');
}// 现在必须这样
function formatDate(date: string | Date | number): string {
  return dayjs(date).format('YYYY-MM-DD');
}

有同事抱怨:"我就写个工具函数,还要标注类型?以前 5 分钟搞定,现在要 15 分钟。"

第二个月:any 成了审查红线

一位老同事习惯了 JavaScript 的灵活性,写了这样一段代码:

TypeScript

 复制代码const data: any = await fetchSomething();
data.something.map(...);

审查直接被打回:"不要用 any,改用 unknown 然后做类型守卫。"

老同事私下跟他说:"你们这些写 TypeScript 的,是不是看不得别人用 any?"

他回答:"不是看不得,而是 any 会让 TypeScript 变回 JavaScript,那我们引入 TS 的意义就完全丧失了。"

老同事回了他一个微笑表情,但他能感觉到对方内心在吐槽他。

第三个月:全组 PR 通过率暴跌

这是最让人意外的一点。

以前组里 PR 的通过率大概在 85% 左右,大部分代码一次就能过,最多小修小改。现在直接掉到了 40%。不是代码质量变差了,而是审查标准变得异常严格

以前审查者的心态是:"能跑就行,别出线上问题。"

现在的心态变成了:"这个类型推导是否合理?这个联合类型能不能定义得更精确?这个泛型参数有没有办法简化?"

有个实习生提交了一段代码,审查来回改了 7 次。最后实习生哭了,说:"我只是想加个按钮,为什么比写毕业论文还难?"

五、反思:TypeScript 到底带来了什么?

好处,是实实在在的:

  1. 线上 bug 显著减少。以前每个月会出现 3-5 个与 undefined 相关的报错,现在基本归零。
  2. 重构时胆子变大了。以前修改一个函数签名,需要全局搜索所有调用位置。现在改完,编译器直接告诉你哪里出了问题。
  3. 新人上手速度变快了。接口文档和类型定义合二为一,新同事看代码时不需要再猜测字段含义了。

代价,也同样真实存在:

  1. 开发效率有所下降。前期编写类型、修复类型错误的时间,大约占了总工时的 20% 到 30%。
  2. 团队需要投入学习成本。3 位同事自费购买了 TypeScript 课程,2 位老同事至今还在用 as any 绕过类型检查。
  3. 审查环节的摩擦增加了。以前审查是"找 bug",现在变成了"辩论类型设计",有时候为了一个 interfacetype 的选择就能争论半天。

六、给想引入 TypeScript 的团队的忠告

1. 不要偷偷摸摸地加,要提前沟通

这次能成功,纯属运气好——组长正好也有推动技术升级的意愿。如果组长是个保守派,可能就会变成"那个擅自更改技术栈的刺头"了。

2. 采用渐进式迁移,不要一刀切

allowJs: true 是救命稻草。让 JavaScript 和 TypeScript 共存,老代码保持不动,新代码使用 TS,这是最现实的落地方式。

3. strict 模式可以晚点再开启

先让 TypeScript 跑起来,再逐步收紧规则。一上来就开启 strict: true,团队会被海量报错信息淹没,直接劝退。

4. 类型定义是团队的公共资产

他把 types/api.ts 抽成了一个独立的包,供全公司共享使用。这样大家无需重复定义,也保证了接口类型的一致性。

5. 学会接受"不完美"的 TypeScript

有些老代码,类型确实难以写清楚,那就用 // @ts-ignore 或者 as 来处理。不要为了追求 100% 的类型覆盖率,把代码改得面目全非、难以阅读。

七、写在最后

三个月后的今天,全组 12 个人里,有 10 个已经习惯了 TypeScript 的开发方式。剩下 2 位老同事仍在坚持使用 JavaScript,但他们的代码审查通过率确实明显低于其他人。

上周 CTO 在全员大会上说:"我们前端团队的代码质量最近提升非常明显,代码审查的深度和严谨度值得其他组学习。"

组长在会上看了他一眼,眼神里透着复杂。

他知道组长想说的是: "是你小子把审查标准抬高的吧?"

来源:https://juejin.cn/post/7660452019567870006
上一篇Vue甘特图vxe-gantt小时视图设置日期轴间隔3小时 下一篇Vite多入口Vue组件库首屏优化:从17MB到按需加载
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
JavaScript有效防止原型污染影响属性存在性检查
前端开发 · 2026-07-18

JavaScript有效防止原型污染影响属性存在性检查

防止原型污染,进行属性存在性检查应使用`Object hasOwn()`绕过原型链上的属性,兼容旧环境时则使用`Object prototype hasOwnProperty call()`。避免使用`in`操作符或直接值判断。治本之策是移除`__proto__`等危险键,用`Object create(null)`创建无原型链的对象,并冻结`Object

如何利用Promise简化前端状态管理的高效实用方法
前端开发 · 2026-07-18

如何利用Promise简化前端状态管理的高效实用方法

Promise的三种状态与链式调用天然适合组织异步流程,结合Promise withResolvers可解耦创建与决议。在UI框架中,Promise实例可直接驱动声明式状态渲染,减少手动维护变量,让状态管理回归本质,并提升代码可读性与可维护性,实现直观的异步状态驱动。

JavaScript内存泄露常见代码模式识别方法
前端开发 · 2026-07-18

JavaScript内存泄露常见代码模式识别方法

JavaScript内存泄漏源于对象不可达却被强引用,典型模式包括意外全局变量、未清理的定时器、DOM引用残留、闭包长期持有大对象及模块静态引用累积。需借助DevTools堆快照定位持续增长的对象与保留者。

JavaScript展开运算符与深拷贝的不可变数据实现对比
前端开发 · 2026-07-18

JavaScript展开运算符与深拷贝的不可变数据实现对比

展开运算符仅浅拷贝第一层属性,对嵌套对象无效但执行高效;深拷贝递归复制所有层级实现完全隔离,但存在性能开销与循环引用问题。实际开发中推荐组合展开运算符与手动深层覆盖,或借助Immer库,仅在嵌套极深时使用structuredClone。

V8引擎编译优化对JavaScript内存占用的影响
前端开发 · 2026-07-18

V8引擎编译优化对JavaScript内存占用的影响

V8引擎的编译优化通过字节码紧凑表示节省约一半内存,隐藏类共享减少对象结构开销,TurboFan编译器执行常量折叠、死代码消除、内联及类型反馈精简表示,分代垃圾回收采用新生代半空间复制与老生代增量标记,有效控制内存峰值并加速短期对象回收。