从 JavaScript 到 TypeScript:我的 30 分钟上手实战经历
背景介绍:为什么我们需要拥抱 TypeScript?

大概一年前,我加入了一个中型前端项目组,这个项目已经运行了将近两年。项目结构比较复杂,包含多个模块、第三方插件以及大量的异步逻辑。刚开始接手代码时,说实话内心是崩溃的——JavaScript 写得飞起,但很多变量、函数参数都没有明确类型,稍不注意就容易传错值,调试起来非常耗时。
当时我们团队面临着几个比较严重的问题:
- 频繁的 runtime 类型错误,比如某个方法期望接收一个对象,结果却收到
undefined。 - 组件之间耦合度高,修改一处影响多处,没有很好的编译期检查机制。
- 新成员上手慢,因为缺乏清晰的类型定义,阅读代码像在猜谜。
为了提升项目的可维护性和开发效率,我们决定引入 TypeScript,也就是 TS。而我当时作为一个 JS 老兵,对 TypeScript 并不太熟悉,甚至有些抵触:“写个变量还要加类型?这不是拖慢开发速度吗?”
然而,在接下来的一个月里,我的观念彻底改变了。通过一次小型重构项目,我在大约 30 分钟内快速完成了核心部分向 TypeScript 的迁移,并在这个过程中深刻体会到了类型系统带来的好处。
今天我就来分享一下那次“30分钟快速上手 TypeScript”的真实经历,希望能帮到正在观望或准备上手的你。
问题描述:老旧项目中的常见痛点

当时我们负责的是一个管理后台项目,技术栈是 React + Redux + Ant Design,整体采用 JavaScript 编写。
有几个典型的问题困扰着我们:
状态对象嵌套深、字段不确定,比如:
const user = { id: 1, name: 'John', address: { city: 'Beijing' } };某天需求变化后,
user.address可能会变成null或者被完全删除,结果某个组件调用.city就直接报错了。函数参数无约束,比如:
function formatAmount(amount, currency) { ... }这两个参数可以是数字、字符串,甚至可能是
null,不同地方调用方式也不统一,导致 bug 频出。接口返回数据结构模糊,API 文档更新滞后,前端需要靠打印
console.log才知道有哪些字段。
这些情况在多人协作中特别容易出问题,严重影响开发效率和质量。
解决方案:为什么选 TypeScript?
TS 其实不是一门新语言,而是 JavaScript 的超集,你可以把它理解成一种带类型支持的更健壮的 JS。
我们选择 TS 的几个关键原因:
- 类型安全:提前发现大多数类型错误。
- 可维护性更高:清晰的类型定义让代码更容易理解和维护。
- IDE 支持强大:智能提示、自动补全、跳转定义等功能大幅提升编码效率。
- 渐进式迁移:不需要一次性重写所有代码,支持逐步改造。
于是我们开始尝试将项目中的一部分组件或工具函数先改造成 TypeScript。
30 分钟上手实战过程
第一步:搭建开发环境(5分钟)
我们的项目是用 CRA(Create React App)构建的,幸运的是从 CRA v2 开始就已经原生支持 TypeScript 了。
只需要执行以下命令:
npx create-react-app my-ts-app --template typescript
如果你的项目还不是 TS 项目也没关系,可以通过下面步骤逐步添加:
npm install --save typescript @types/react @types/node @types/react-dom
然后创建 tsconfig.json 文件,这是 TS 的配置文件,我们使用默认配置:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
✅ 小技巧:开启
strict模式是非常推荐的做法,它能帮你捕获更多潜在错误。
第二步:从一个简单组件开始改造(10分钟)
我们就拿最简单的 UserCard 组件作为实验对象:
function UserCard({ user }) {
return (
<div>
<p>{user.name}</p>
<p>{user.address.city}</p>
</div>
);
}
这个组件接受一个 user 对象,里面有个嵌套的 address 字段。
引入类型定义:
首先,我们在 types/index.ts 中定义 User 类型:
export interface Address {
city: string;
zipcode?: string;
}
export interface User {
id: number;
name: string;
address: Address | null;
}
改造组件:
把原来的 UserCard.jsx 重命名为 UserCard.tsx,并添加类型注解:
import { User } from '../types';
interface Props {
user: User;
}
function UserCard({ user }: Props) {
return (
<div>
<p>{user.name}</p>
<p>{user.address?.city}</p>
</div>
);
}
这里用了可选链操作符 ?. 来防止访问 null 或 undefined 导致的报错。
这时候编辑器已经开始给你反馈了:
- 如果你传进来一个没有
name属性的 user,TypeScript 立即报错; - 如果你不小心写了
user.adress.city(拼写错误),也能马上发现。
第三步:处理 API 接口数据(10分钟)
我们当时的接口都是通过 Axios 请求的,响应数据类型不清楚,只能靠 runtime 判断。
比如一个获取用户详情的方法:
async function fetchUserDetail(userId: number): Promise<any> {
const res = await axios.get(`/api/user/${userId}`);
return res.data;
}
这会导致我们在使用返回结果时,很容易误用不存在的字段。
我们改为:
async function fetchUserDetail(userId: number): Promise<User> {
const res = await axios.get(`/api/user/${userId}`);
return res.data;
}
这样一来,只要返回的数据不符合 User 类型结构,就会有编译错误提醒。
第四步:类型推导 & 泛型实践(5分钟)
在一些通用工具函数中,泛型非常有用。
比如一个通用的格式化金额函数:
function formatAmount<T>(amount: T, currency: string = 'CNY'): string {
if (typeof amount === 'number') {
return `${currency} ${amount.toFixed(2)}`;
}
return `${currency} 0.00`;
}
这样无论传 number 还是 string,都可以正确处理。
踩坑经验总结
虽然整个过程很快,但在实际工作中还是遇到不少“坑”,记录下来供你参考。
🧨 坑 1:any 类型泛滥
初期最容易犯的错误就是随手写 any,这样就等于没加类型约束。
例如:
function process(data: any) {}
这种写法其实并没有起到类型保护作用。后来我们强制约定:除非不得已,不要随便使用 any。替代方案可以用:
unknown:需要你做类型检查后再进行操作。- 明确写出接口或联合类型。
🧨 坑 2:异步函数返回类型混乱
经常有人这样写:
async function getUser(): Promise<User> { ... }
但有时忘了写 Promise,或者返回了一个 number,这时候 TypeScript 是不会允许你通过的。
🧨 坑 3:类型定义太松散
我们曾定义过一个超级宽泛的类型:
type UserInfo = Record<string, any>;
看起来方便,但是失去了类型保护的意义。后来改成按接口具体定义。
🧨 坑 4:Redux 状态类型不匹配
由于 Redux 的 Store 结构嵌套较深,我们一开始只是给 Action 加了类型,State 却没有约束,导致后续 selector 出现各种问题。
最终我们建立了完整的 State 类型定义:
interface AppState {
user: UserState;
loading: boolean;
}
效果总结:收益远超预期
这次迁移完成后,我们获得了以下几个明显的好处:
- 类型错误大幅减少:原本常见的
Cannot read property 'city' of undefined错误几乎消失。 - 新人入职成本降低:看到组件 props 和 state 类型一目了然,沟通成本变低。
- 重构信心提升:有了类型保护之后,我们敢大胆地删减冗余代码。
- 配合 VSCode 插件提升开发效率:自动补全、函数签名提示、跳转定义等,节省了很多时间。
更重要的是:团队成员之间对代码质量的要求也提升了,大家不再容忍“随意传参”这样的坏习惯。
实战建议与注意事项
作为一个经历过转型阵痛期的老程序员,我有一些真心话想跟正在考虑要不要上 TypeScript 的你聊聊:
✅ 必做事项:
- 启用 strict 模式:越早越好,后面你会感谢自己的。
- 分阶段推进:可以从工具函数、小组件入手,逐步推广。
- 统一命名规范:比如类型命名以大写字母开头,接口用
Ixxx。 - 建立共享类型库:把公共类型抽离到
types/index.ts。 - 配合 ESLint/Stylelint 使用:确保团队编码风格一致。
❌ 尽量避免:
- 不要滥用
any - 不要在不了解的情况下使用类型断言 (
as) - 不要过度使用类型推导,适当显式声明更清晰
前端开发者的小贴士
作为一名前端工程师,在转向 TypeScript 的时候,我也积累了一些实用小技巧,希望对你有帮助:
🔍 调试建议:
- 在 VSCode 中安装 TypeScript Plugin for VSCode
- 用 F12 跳转定义,Shift+F12 查找引用,提高效率
- 利用
@ts-expect-error临时忽略某行 TS 报错(仅限特殊场景)
📦 性能优化方向:
- 合理使用
React.memo和useCallback,配合类型系统优化渲染 - 使用 Webpack 分包时,TypeScript 模块合并得当,有助于减少重复打包体积
🌐 浏览器兼容性提示:
- 不要忘了
target设置为es5,确保旧浏览器支持 - 配合 Babel 转义语法,尤其是可选链、空值合并等 ES2020 特性
最后的话:TS 是前端工程化的必经之路
TypeScript 也许不能解决所有问题,但它绝对是现代前端开发不可或缺的一环。
回望当初那个只关心“能不能跑起来”的我,现在我会说:类型是你的朋友。它不仅帮你捕捉错误,更是你和别人协作时最有效的沟通方式。
希望这篇 30 分钟上手指南,能让你少走弯路,早点体验到类型系统的魅力。
如果你正打算迈入 TypeScript 世界,别犹豫,动手写第一个 .ts 文件吧,说不定从此打开新天地。😊
🚀 附录:学习资源推荐
- 官方文档:TypeScript 官网
- 实战教程:TypeScript Tutorial
- 交互练习平台:TypeScript Playground
如有任何交流或疑问,欢迎留言讨论~

评论 0