老项目引入TypeScript意外提升全公司代码审查标准
一位前端工程师为老项目悄悄引入TypeScript,从API层补类型定义并开启strict模式。同事效仿后,全组逐步迁移至TS,代码审查标准随之抬高:类型定义成标配,禁止使用any,PR通过率下降但线上bug减少,重构更安全。
先说一个真实案例。去年第四季度,有位前端开发人员在修改表单提交功能时,遇到了一个棘手的问题。接口返回的数据结构大致如下:

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
}
}
注意,他关闭了 strict 和 noImplicitAny。这是有意的策略——先让 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 到底带来了什么?
好处,是实实在在的:
- 线上 bug 显著减少。以前每个月会出现 3-5 个与
undefined相关的报错,现在基本归零。 - 重构时胆子变大了。以前修改一个函数签名,需要全局搜索所有调用位置。现在改完,编译器直接告诉你哪里出了问题。
- 新人上手速度变快了。接口文档和类型定义合二为一,新同事看代码时不需要再猜测字段含义了。
代价,也同样真实存在:
- 开发效率有所下降。前期编写类型、修复类型错误的时间,大约占了总工时的 20% 到 30%。
- 团队需要投入学习成本。3 位同事自费购买了 TypeScript 课程,2 位老同事至今还在用
as any绕过类型检查。 - 审查环节的摩擦增加了。以前审查是"找 bug",现在变成了"辩论类型设计",有时候为了一个
interface和type的选择就能争论半天。
六、给想引入 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 在全员大会上说:"我们前端团队的代码质量最近提升非常明显,代码审查的深度和严谨度值得其他组学习。"
组长在会上看了他一眼,眼神里透着复杂。
他知道组长想说的是: "是你小子把审查标准抬高的吧?"
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何用JavaScript动态修改页面Meta元数据
使用JavaScript可通过document querySelector定位并修改已有meta标签的content属性,或动态创建meta元素插入文档头部。该方法适用于单页应用更新页面描述、OpenGraph及Twitter卡片数据,但搜索引擎爬虫与社交平台通常不执行JS,动态修改对SEO和分享抓取无效,可靠方案仍需服务端渲染或静态生成。
原生HTML模态对话框焦点陷阱无障碍实现原理
使用`dialog showModal()`可自动实现焦点陷阱与Tab循环,无需JavaScript。纯CSS或`inert`属性无法循环焦点,只有正确声明模态上下文才能满足WCAGA级无障碍标准。
VS Code扩展调试获取当前工作目录与项目根路径
在VSCode扩展开发中,`process cwd()`默认指向扩展目录而非用户项目,且不能`chdir`。应使用`vscode workspace workspaceFolders`API动态获取项目根目录,注意处理空值并避免已弃用的`rootPath`。
React Leaflet地图中心与缩放动态控制实战教程完整版
在React-Leaflet中,通过按钮点击跳转至指定坐标并调整缩放级别,应使用whenCreated获取Leaflet原生地图实例,调用setView或flyTo方法,避免依赖center zoomprops的响应式更新,以确保性能与状态一致性。
如何用事件委托实现动态卡片翻转的原生JS
针对动态生成的卡片列表,采用事件委托将监听器挂载到父容器,利用事件冒泡精准捕获点击目标。通过class替代重复ID,使用card querySelector()限定局部作用域,配合closest()定位卡片容器,实现翻转与还原功能。该方法避免全局查询与状态污染,确保多卡片独立控制,提升性能与可扩展性。
- 热门数据榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-18 18:23
2026-07-18 18:23
2026-07-18 18:22
2026-07-18 18:19
2026-07-18 18:18
2026-07-18 18:18
2026-07-18 18:18
2026-07-18 18:18
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

