从 JavaScript 到 TypeScript:我的 30 分钟上手实战经历

Issue终结者
2025-06-28 18:08
阅读 385

背景介绍:为什么我们需要拥抱 TypeScript?

背景介绍:为什么我们需要拥抱 TypeScript?

大概一年前,我加入了一个中型前端项目组,这个项目已经运行了将近两年。项目结构比较复杂,包含多个模块、第三方插件以及大量的异步逻辑。刚开始接手代码时,说实话内心是崩溃的——JavaScript 写得飞起,但很多变量、函数参数都没有明确类型,稍不注意就容易传错值,调试起来非常耗时。

当时我们团队面临着几个比较严重的问题:

  • 频繁的 runtime 类型错误,比如某个方法期望接收一个对象,结果却收到 undefined
  • 组件之间耦合度高,修改一处影响多处,没有很好的编译期检查机制。
  • 新成员上手慢,因为缺乏清晰的类型定义,阅读代码像在猜谜。

为了提升项目的可维护性和开发效率,我们决定引入 TypeScript,也就是 TS。而我当时作为一个 JS 老兵,对 TypeScript 并不太熟悉,甚至有些抵触:“写个变量还要加类型?这不是拖慢开发速度吗?”

然而,在接下来的一个月里,我的观念彻底改变了。通过一次小型重构项目,我在大约 30 分钟内快速完成了核心部分向 TypeScript 的迁移,并在这个过程中深刻体会到了类型系统带来的好处。

今天我就来分享一下那次“30分钟快速上手 TypeScript”的真实经历,希望能帮到正在观望或准备上手的你。


问题描述:老旧项目中的常见痛点

问题描述:老旧项目中的常见痛点

当时我们负责的是一个管理后台项目,技术栈是 React + Redux + Ant Design,整体采用 JavaScript 编写。

有几个典型的问题困扰着我们:

  1. 状态对象嵌套深、字段不确定,比如:

    const user = {
      id: 1,
      name: 'John',
      address: {
        city: 'Beijing'
      }
    };
    

    某天需求变化后,user.address 可能会变成 null 或者被完全删除,结果某个组件调用 .city 就直接报错了。

  2. 函数参数无约束,比如:

    function formatAmount(amount, currency) { ... }
    

    这两个参数可以是数字、字符串,甚至可能是 null,不同地方调用方式也不统一,导致 bug 频出。

  3. 接口返回数据结构模糊,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>
  );
}

这里用了可选链操作符 ?. 来防止访问 nullundefined 导致的报错。

这时候编辑器已经开始给你反馈了:

  • 如果你传进来一个没有 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.memouseCallback,配合类型系统优化渲染
  • 使用 Webpack 分包时,TypeScript 模块合并得当,有助于减少重复打包体积

🌐 浏览器兼容性提示:

  • 不要忘了 target 设置为 es5,确保旧浏览器支持
  • 配合 Babel 转义语法,尤其是可选链、空值合并等 ES2020 特性

最后的话:TS 是前端工程化的必经之路

TypeScript 也许不能解决所有问题,但它绝对是现代前端开发不可或缺的一环。

回望当初那个只关心“能不能跑起来”的我,现在我会说:类型是你的朋友。它不仅帮你捕捉错误,更是你和别人协作时最有效的沟通方式。

希望这篇 30 分钟上手指南,能让你少走弯路,早点体验到类型系统的魅力。

如果你正打算迈入 TypeScript 世界,别犹豫,动手写第一个 .ts 文件吧,说不定从此打开新天地。😊


🚀 附录:学习资源推荐

如有任何交流或疑问,欢迎留言讨论~

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝