让 AI 升级一个 4 年前的 React 项目:从 16 到 18 的完整记录

把一个 React 16 的老项目升级到 React 18,手动做至少要一周。本文记录了用 AI 工具辅助完成依赖升级、Breaking Changes 修复、测试验证的全过程,以及中间踩过的 5 个坑。

起因:一个"还能跑"的项目,直到它跑不动了

手头有一个内部使用的运营后台,2022 年初用 Create React App + React 16 搭的,依赖了十几个老旧的 npm 包。因为一直能用,加上业务需求不断,升级这件事就一拖再拖。

直到上周,一个关键的安全漏洞扫描工具直接标红了项目中的 react-scriptswebpack,CI 流水线被卡住,不升级就不让部署。我打开 package.json 一看:

json 复制代码
{
  "react": "^16.14.0",
  "react-dom": "^16.14.0",
  "react-router-dom": "^5.2.0",
  "antd": "^4.16.0",
  "react-scripts": "4.0.3",
  ...
}

4 年前的依赖树,中间横跨了两个大版本。React 16 到 18,React Router 5 到 6,Ant Design 4 到 5------每一个都是 Breaking Changes 的重灾区。

如果手动做,我估计要花一周:一个一个查升级文档、改语法、处理废弃 API、跑测试、修边界 case。这次我决定换一种方式:让 AI 做主力,我做把关人

准备工作:先让 AI 生成一份"升级风险评估报告"

在动手改代码之前,我需要知道这场升级的难度到底有多大。最怕的情况是改到一半发现某个底层依赖不兼容,整条路走不通。

我把 package.json 和项目的大致架构描述(技术栈、主要功能、路由数量、状态管理方案)贴给了 ChatGPT,问了三个问题:

提示词

markdown 复制代码
我有一个 CRA + React 16 + Antd 4 + React Router 5 的项目,需要升级到 React 18 + Antd 5 + React Router 6。

请分析:
1. 列出所有已知的 Breaking Changes,按风险等级(高/中/低)分类。
2. 给出推荐的升级顺序(先升哪个、后升哪个)。
3. 标记哪些包可能需要替换或移除。

它返回了一份相当详细的清单,我截取核心部分:

  • 高风险:React Router 5→6(路由写法完全改变)、Antd 4→5(样式体系从 Less 变成 CSS-in-JS,大量组件 API 变更)。
  • 中风险:React 18 的 createRoot 替代 ReactDOM.render、Suspense 行为变更、自动批处理可能影响某些依赖 state 时序的旧代码。
  • 低风险:React 16 的 class component 仍然兼容,无需强制改 hooks。

更重要的是,它给出了升级顺序建议:

markdown 复制代码
1. 先升级 React 到 18,保持其他包不变,处理 createRoot 和废弃 API。
2. 再升级 React Router 到 6,重写所有路由。
3. 最后升级 Antd 到 5,处理样式和组件 API。
4. 每一步升级后跑测试,确认功能正常再继续。

这个顺序至关重要。如果一次性把所有包版本号改了,报了几百个错误根本没法定位。分步升级、每一步验证,是这次能成功的关键策略。

第一步:React 16 → 18,最顺利的一步

我修改了 package.json 中的 reactreact-dom 版本:

json 复制代码
{
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

npm install 之后,项目直接报了两个错误:

  1. ReactDOM.render is no longer supported in React 18.
  2. Warning: ReactDOM.render is no longer supported

这是预料之中的。我在 Cursor 中打开 src/index.js,原代码:

jsx 复制代码
import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));

我用 Cursor 的 Cmd+K 输入:

复制代码
换成 React 18 的 createRoot 写法

Cursor 自动替换为:

jsx 复制代码
import { createRoot } from 'react-dom/client';

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

这一个改动解决了所有 React 入口问题。接着跑了一遍测试------全部通过。这得益于 React 团队出色的向后兼容性。第一步耗时:不到 10 分钟。

第二步:React Router 5 → 6,最痛苦的一步

这一步是我最恐惧的。项目里有 30 多个路由,分布在 5 个文件中,大量使用 <Switch><Redirect>useHistory()withRouter() 这些在 v6 中被移除或彻底改变的 API。

我选择先用 AI 扫描所有路由文件,生成一份"需要修改的代码清单",再逐文件改。

扫描提示词(在 Cursor 中对着项目文件夹问):

复制代码
找出项目中所有使用 React Router v5 API 的地方,列出文件名、行号、使用的旧 API,并给出 v6 的等价写法。

Cursor 索引了整个项目,返回了这样一份清单(局部):

文件 行号 旧 API 新写法
App.js 12 <Switch> <Routes>
App.js 15 <Redirect> <Route path="*" element={<Navigate />} />
useAuth.js 8 useHistory() useNavigate()
PrivateRoute.js 5 component prop element prop
UserList.js 22 withRouter() useParams() / useLocation()

有了这张表,我逐个文件修改。以最复杂的 App.js 为例,原始代码:

jsx 复制代码
import { Switch, Route, Redirect } from 'react-router-dom';

<Switch>
  <Route exact path="/" component={Dashboard} />
  <Route path="/users" component={UserList} />
  <Route path="/orders" component={OrderList} />
  <Redirect from="/old-dashboard" to="/" />
  <Route component={NotFound} />
</Switch>

我在 Cursor 中选中这段代码,输入:

ini 复制代码
换成 React Router v6 写法,保留所有路由逻辑,NotFound 用 path="*"

输出:

jsx 复制代码
import { Routes, Route, Navigate } from 'react-router-dom';

<Routes>
  <Route path="/" element={<Dashboard />} />
  <Route path="/users" element={<UserList />} />
  <Route path="/orders" element={<OrderList />} />
  <Route path="/old-dashboard" element={<Navigate to="/" replace />} />
  <Route path="*" element={<NotFound />} />
</Routes>

踩坑 1<Navigate> 需要加 replace 属性,否则浏览器后退按钮行为异常。AI 第一次没加,我手动补上了。

踩坑 2useHistory() 改成 useNavigate() 后,原来的 history.push('/path') 变成 navigate('/path'),但 history.replace('/path') 需要写成 navigate('/path', { replace: true })。AI 在几处漏掉了这个细节,我用全局搜索 history.replace 才找出来。

踩坑 3withRouter() 被移除后,一个老旧组件原本用 this.props.history,改成 useNavigate() 意味着必须把 class component 改成 function component。AI 给出了重构方案,但我选择了更保守的做法:保持 class component,用自定义 HOC 替代 withRouter

整个路由迁移用了大约 1 小时,远比我自己查文档、对照 API 要快。AI 的价值不是零失误,而是把 90% 的机械替换工作做掉,剩下的 10% 边界 case 由人来修复

第三步:Ant Design 4 → 5,最意外的一步

Antd 5 的改动比我想象中大得多。官方提供了一个 @ant-design/codemod 工具来自动迁移,但我跑了一遍,只修复了约 60% 的问题。剩下的 40% 是一些冷门组件和自定义样式的问题。

问题一:样式体系彻底改变

Antd 4 依赖 Less 变量和 antd/dist/antd.css 全局引入。Antd 5 改用 CSS-in-JS(基于 @ant-design/cssinjs),不再需要全局 CSS。

删除 import 'antd/dist/antd.css' 后,项目里的自定义 Less 变量全部失效。我原来用 @primary-color 覆盖主题色,现在必须改用 ConfigProvidertheme 属性。

我在 Cursor 中选中 src/App.js,输入:

bash 复制代码
用 Antd 5 的 ConfigProvider + theme 替代原来的 Less 变量覆盖,主题色是 #1890ff

Cursor 自动生成了:

jsx 复制代码
import { ConfigProvider } from 'antd';

const theme = {
  token: {
    colorPrimary: '#1890ff',
    borderRadius: 6,
  },
};

<ConfigProvider theme={theme}>
  <App />
</ConfigProvider>

所有原来依赖 @primary-color 的 Less 变量需要全局替换成 CSS 变量或直接使用 token。这部分我没让 AI 批量改,因为自定义样式太分散,手动逐文件调整反而更安全。

问题二:组件 API 变更

Antd 5 中许多组件的 API 做了破坏性变更。比如:

  • Tablepagination 属性从 pagination={{ pageSize: 10 }} 改为 pagination={{ pageSize: 10 }} 仍然兼容,但 showSizeChanger 的默认值变了。
  • Modalvisible 改为 open
  • DatePickermoment 被废弃,改用 dayjs(这又涉及另一个依赖替换)。

我让 Cursor 扫描项目中所有的 visible 属性:

arduino 复制代码
找出所有 antd 组件中使用的 visible 属性,改成 open

它找到了 12 处,分布在 8 个文件中,全部自动修改。

问题三:moment.js → dayjs

Antd 5 默认使用 dayjs 替代 moment,并推荐移除 moment 依赖。项目中有两处直接使用了 moment 做日期格式化,不能简单删除包。

我让 Cursor 把这两处替换为 dayjs 的等价写法:

js 复制代码
// 原来
import moment from 'moment';
moment(date).format('YYYY-MM-DD HH:mm:ss')

// 改为
import dayjs from 'dayjs';
dayjs(date).format('YYYY-MM-DD HH:mm:ss')

API 兼容,直接替换即可。

整体 Antd 迁移用了约 2 小时,比路由升级更耗时,主要因为大量视觉回归测试------每个页面都要打开看一遍,确认组件样式没有崩。

第四步:其他依赖的连锁反应

升级过程中还遇到几个次要但卡壳的问题:

  1. react-scripts 从 4.x 升到 5.x :Webpack 配置不再兼容,之前 eject 过的项目需要手动合并配置。我让 Cursor 对比了两个版本的 webpack.config.js diff,合并后解决了问题。

  2. @testing-library/react 需要从 12 升到 14render 函数的包裹方式变了,一些测试用例需要加 act() 包裹异步状态更新。AI 帮我扫描了所有测试文件,标出了需要加 act() 的地方。

  3. Node.js 版本 :CRA 5 要求 Node >= 16,项目原来用的 Node 14。这个不是 AI 能解决的,自己改了 .nvmrc

AI 辅助升级的协作模式总结

经过这一轮升级,我总结出一套可以复用的模式:

环节 AI 负责 人负责
风险评估 生成 Breaking Changes 清单、推荐升级顺序 审核清单是否遗漏、决策升级顺序
代码修改 批量替换废弃 API、生成等价写法 审查 diff、补充 AI 遗漏的边界 case
错误修复 解释报错原因、给出修复建议 判断修复方案是否适用于当前上下文
测试 生成测试用例、标记需要加 act() 的地方 跑测试、做视觉回归测试
构建/部署 分析构建错误、修复配置 最终确认构建产物正常

整个升级最终耗时约 5 小时(加上休息和复查)。相比之下,如果纯手工做,我估计要 20-30 小时。AI 的价值是把"查找文档→对照 API→机械替换"这个循环压缩到了秒级,但最终的架构决策和边界验证仍然需要人。

五个最重要的踩坑教训

  1. 不要一次性改完所有 package.json。分步升级,每一步都跑测试,出问题能立刻定位。贪快的结果是面对几百个报错无从下手。

  2. AI 给出的迁移脚本需要逐行审查 。AI 可能会漏掉 replaceasync act() 这样的细微语义差异,这些在编译期不报错,运行时才暴露。

  3. Antd 5 的样式迁移是最耗时的部分。如果项目大量自定义了 Less 变量,建议先在一个页面试点迁移,摸清 token 机制后再全量推进。

  4. 保留 Git 的每一步提交 。我在每一步升级后都打了一个 commit:chore: upgrade react to 18refactor: migrate to react router v6chore: upgrade antd to 5。这样出问题可以随时回到上一个稳定状态。

  5. 用两个 AI 工具互相验证 。在路由迁移时,我同时问了 ChatGPT 和 Cursor 同一个问题:withRouter 的替代方案。两者给出的答案不同------一个建议用 Hooks 重写组件,另一个建议保留 class component 用自定义 HOC。最终我选择了更保守的方案,这在当时是正确的决策。

最后

这次升级让我意识到一件事:老旧项目的升级最难的不是改代码,而是理解"当初为什么要这样写" 。AI 能帮你改语法、换 API,但它不知道那个 withRouter 之所以没改成 Hooks,是因为它依赖了另一个 class component 的生命周期逻辑。 有些上下文永远在人脑子里。AI 是加速器,但方向盘还是得自己握着。

参考来源

文中使用的各模型 API Key 均可从 gpt108.com 获取(该渠道提供 ChatGPT Plus、Claude Pro、Gemini Advanced、Cursor Pro 及 API 充值服务)。笔者团队生产环境已稳定运行 4 个月,仅作技术方案记录

你最近有升级过老项目吗?有没有用到 AI 辅助?踩了哪些坑?欢迎评论区聊聊。

相关推荐
爱吃的小肥羊6 小时前
谷歌I/O解读:小模型反杀旗舰,3.5 Flash凭什么全面超越3.1 Pro?
aigc·ai编程
彦为君7 小时前
长时间运行的 Agent:如何设计可靠的执行框架
python·ai·ai编程
子昕7 小时前
看完 Google I/O 2026,我确信:多 Agent 时代不是概念了,Google 在造基础设施
ai编程
极品小學生7 小时前
拆解大模型时代的“流量交通枢纽”:API 中转站架构与核心原理
ai·架构·ai编程
kyriewen7 小时前
Copilot下个月按Token收钱,我算了一笔账:重度用户一年要多花3000块
前端·javascript·openai
uccs7 小时前
Agent循环原理
agent·ai编程·claude
盼君8 小时前
AI生成了网页,怎么部署上线?从零到HTTPS全流程实录
ai编程
深度先生8 小时前
01 Chroma_环境与uv极速起手
ai编程
codingWhat8 小时前
让AI来帮团队code review,我是这样做的(已开源)
ai编程