说真的,看到这个标题你可能觉得我在炫技。但实际情况是:接到这个 Vue2 到 Vue3 迁移任务的时候,我内心比谁都慌。业务方给的时间窗口只有四天,代码库存量却是一点都不小——200 多个单文件组件、几十个路由页面、三四个状态管理模块,还夹着若干“祖传插件”和内部二次封装的组件库。如果按传统流程来,先手动梳理依赖,再逐个文件改 API,最后统一回归,四天绝对不现实。
所以这次我换了个思路,大规模引入 AI 辅助。Claude 负责大批量代码扫描、差异改写和逻辑解释,MiniMax 负责生成审阅摘要、补充命名方案和协助差异比对,我只需要做架构决策、人肉审核和最终回归验证。这四天里,实际验证下来这套组合是能跑通的,而且效果超出预期。这篇文章不打算写什么“AI 取代程序员”的空话,我只想把这套 AI 辅助迁移的工具链、提示词模板、批量改写流程和踩过的坑,原原本本分享出来,给正在做同类迁移的团队一个可参考的脚本。
1. 项目整体情况与迁移思路
1.1 项目体量与迁移难点
先说说这个项目到底有多大。仓库本身是 Vue 2.6 搭建的 SPA 应用,路由用的是 vue-router 3.x 的 history 模式,状态管理是 Vuex 3,UI 框架是基于 Element UI 做的内部二次封装,自己维护了几套业务组件。整体规模大概是:200 多个 .vue 文件、40 多个页面级路由、10 个左右全局混入和自定义指令,另有三个模块的 Vuex store。
这种项目的最大难点,不是单个文件不好改,而是“改动点分布在每一层”。组件里既要改模板语法,又要改 script 里的 Options API,还要处理全局 API 的变更;路由入口要改;store 的创建方式要改;构建工具更得整个换。任何一个环节漏掉,页面要么白屏,要么编译失败,要么运行时直接报 undefined。更麻烦的是,项目里还存在不少历史遗留代码,比如已经废弃的过滤器语法、混用了$on的跨组件通信、按需加载的异步组件写法……这些光靠搜索替换解决不了,必须让人读懂逻辑后手动判断。
我一开始也想过用传统方式“稳妥推进”,但估算下来,光是把所有组件扫一遍并理解业务逻辑,就至少得花一个人将近两周。预算和排期都不允许。所以从立项开始,我就决定用 AI 来做“编码助理”,把人力从重复改写里释放出来,集中投在审核和业务验证上。
1.2 四天时间为什么可行
四天听起来很紧张,但如果把工作拆细,其实是能排开的。我做的第一件事,就是把整个迁移拆成四个阶段:盘点、试点、批量迁移、回归验证。第一阶段用大半天,主要做依赖整理、全仓扫描和风险点登记;第二阶段用半天,挑一个代表性页面跑通全链路,验证 Claude 的改写质量和构建配置是否成立;第三阶段是工作量重心,花两天时间用 AI 批量改写组件、路由和 store,并同步做编译修复;最后一天专门留给回归验证、修显性问题和小范围兼容处理。
这个排期的逻辑在于:前两步相当于“探路”,如果试点跑不通,后面批量执行再快也是白搭。我最担心的问题从来不是 AI 改写速度,而是“AI 生成的代码能不能进编译、跑通页面”。所以试点阶段我一定会自己锁定一个包含路由、store、组件嵌套、异步请求的页面,完整验证一遍工具链。
1.3 为什么首选 Claude + Minimax 这套组合
其实市面上能用的大模型不少,我也不是没试过其他方案。最终选定 Claude 和 MiniMax,主要出于三个互补性考虑。
Claude 的核心优势是长上下文和代码理解能力。200 多个文件的迁移项目,文件之间互相依赖,我需要模型能一次性读入整个目录结构、理解 A 组件引用了 B store 的某个 mutation,再给出改写意见。常规模型面对几千行代码时容易语义飘移,但 Claude 在超长字段上表现很稳,写出的大段.vue文件逻辑结构完整,可读性好。尤其是v-model这类行为有细节差异的语法,它给出的解释和改写都足够精准。
MiniMax 在这个流程里的角色,更多是“审阅助理”。我已经用它的 API 写了一小段批处理脚本,扫描迁移后的临时文件,让它生成问题摘要和差异说明。生成这类中文解释摘要时,它的描述质量很高,能帮我们快速定位“哪些文件被 AI 写坏了”“哪些文件我根本没改到”。另外,如果你手头有本地部署的 MiniMax H 系列权重包,也可以把同样的脚本打到本地推理服务上,跑大批量扫描时几乎零成本,很适合当“质检员”角色。总体而言,大模型不一定要选“最贵的”,选适合流水线分工的,效果往往更好。
2. AI 辅助迁移的工具链搭建
2.1 Claude Code 的安装与权限设置
我在迁移中用的主力交互工具是 Claude Code。它本质是一个运行在终端里的 AI 编码代理,可以读取项目文件、执行命令、生成 diff,很适合这种“站在项目根目录写作”的场景。安装方式很简单,只要本地有 Node.js 环境,执行一条命令即可:
npm install -g @anthropic-ai/claude-code安装完成后,在项目根目录执行claude就能进入交互模式。第一次启动它会检查认证信息,配置好你的 API 或订阅凭据就行。这里有个容易被忽略的细节:Claude Code 需要知道你允许它做哪些事。启动后它会询问是否允许读写文件和执行命令,我的做法是在项目目录内逐项授权,不图省事开全局权限。
为什么我特别强调权限粒度?因为在迁移场景里,AI 会读很多老文件、生成一大堆新文件,万一误删或覆盖不该动的东西,损失非常大。逐项目授权、保留审批环节,虽然看起来麻烦,实际上只增加了十几分钟的确认成本,却可以避开灾难性的返工。
交互过程中我基本只用两类指令:常规对话提需求,以及/compact压缩上下文。迁移项目跑久了,对话历史会非常长,如果不加控制,模型容易丢失早期需求细节。大概每处理 20 个文件之后,我就会主动压缩一次上下文,把已经完成的工作状态重新概述一遍,再继续下一批任务。
2.2 MiniMax 的接入与批量审阅脚本
Claude 负责“写”,MiniMax 负责“审”和“总结”,这个分工合理之后,我写了一个很薄的 Python 脚本,用来批量调用 MiniMax 接口,对迁移结果做第一轮质检。
脚本并不复杂,核心是用 requests 发起对话请求。因为现在不少模型都提供 OpenAI 兼容的接口格式,所以我干脆按照这种格式去组织请求,只要把模型的 base_url 和密钥换成对应服务的配置就行。如果你本地跑着 MiniMax 的模型权重,也可以把 base_url 指向本地推理服务,跑批量扫描时成本基本可以忽略。
import requests API_KEY = "你的密钥" BASE_URL = "https://api.minimax.chat/v1/text/chatcompletion_v2" MODEL_NAME = "MiniMax-Text-01" def review_file(file_path, content): resp = requests.post( BASE_URL, headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": MODEL_NAME, "messages": [ {"role": "system", "content": "你是资深 Vue 迁移审阅助手。你只做一件事:分析给定的 Vue 文件内容,列出所有疑似迁移遗漏、语法错误或与 Vue3 不兼容的地方,按严重程度排序,输出简洁的中文问题清单,不要修改代码。"}, {"role": "user", "content": f"文件路径:{file_path}\n\n文件内容:\n{content}"} ] } ) return resp.json()["choices"][0]["message"]["content"]实际使用中,我会先把一个文件用 Claude 改写,然后把改写后的结果喂给 MiniMax 做审阅。MiniMax 经常能挑出两类问题:一类是 Claude 在改写时把变量名写错或漏引入,另一类是注释里还残留 Vue2 术语、文档链接等旧信息。这比我人工逐字看效率高太多。
需要提醒的是,这个脚本里的模型名和接口地址记得以官方最新文档为准,不同版本的服务商可能会有差异。但整体思路是可以复用的:AI 辅助迁移不是只靠一个模型,而是让两个模型互相交叉检查,把出错概率降下来。
2.3 提示词模板与任务拆解
工具链搭好后,真正决定迁移质量的是提示词。我在这四天里反复打磨出了几套固定模板,这里挑三套最重要的分享。
第一套是“全仓扫描模板”,用在项目开始时。目的是让 Claude 先浏览整个目录结构,输出一个迁移风险清单,而不是一上来就动手改代码。我的写法是:
你是资深 Vue 工程师。请扫描项目 src 目录,列出所有涉及 Vue2 特有 API 的文件和代码片段。重点查找: - Options API 中的 filters、$set、$delete、$on、$off - this.$scopedSlots 的使用 - 基于 Element UI 的组件二次封装 - vue-router 3 和 Vuex 3 的引入方式 - 模板中的 .sync 修饰符和具名插槽默认写法 输出格式:文件路径 + 风险描述 + 建议改法。第二套是“单文件改写模板”,用于批量迁移组件。它是全流程里最核心的提示词,我要求模型必须保持组件的外部接口不变:
将以下 Vue2 Options API 组件改写为 Vue3 Composition API 语法。 要求: 1. props、emit、v-model 等对外接口保持完全不变。 2. 使用 <script setup> 语法。 3. 保留原有业务逻辑和注释,不进行额外重构。 4. 如果原组件使用了 computed 和 methods 且互相依赖,拆解后保持调用关系不变。 5. 输出文件末尾列出所有“不确定项”,例如 this.$nextTick 的时序差异、watch 深层监听行为、旧组件库的特殊用法。第三套是“构建错误修复模板”。迁移过程中经常出现编译报错,这些错误比较机械,适合让 AI 直接处理。我会把完整报错日志粘贴给它,并附加相关文件的上下文。Claude 处理这类问题的准确率很高,比我反复看终端日志找问题快得多。
3. 核心迁移的实操环节
3.1 依赖与构建工具的一次性升级
任何 Vue2 到 Vue3 的迁移,第一步都必须先把依赖和构建工具理顺。这个环节不建议用 Claude 直接改 package.json,因为语义化版本号很容易搞混。我选择自己先拉一把全量清单,把关键依赖的版本对应关系列成表,逐项确认后交给 AI 辅助修改。
下表是我这次迁移中用到的核心依赖对照:
| 一项 | Vue2 版本 | Vue3 版本 | 迁移注意点 |
|---|---|---|---|
| vue | 2.6.x | 3.4.x | 直接升级后先跑通空项目再叠加改动 |
| vue-router | 3.x | 4.x | 创建方式从 new Router 变成 createRouter |
| vuex | 3.x | 4.x 或弃用 | 更推荐直接改用 Pinia,少一层心智负担 |
| vue-cli | 5.x | 不用 | 建议整体切换到 Vite,后续收益远大于迁移成本 |
| element-ui | 2.x | element-plus | 组件 API 有变化,二次封装层需要做适配 |
| vue-template-compiler | 2.x | 移除 | 该包只服务 Vue2,在 Vue3 下没有对应角色 |
| babel-plugin-component | 2.x | 移除或换 unplugin | 按需加载方案需要重写为 unplugin-vue-components |
我特别想多说一句切换到 Vite 的事。很多人保守起见会先保留 Vue CLI 的构建底座,只升级 Vue 版本,想着以后再说。但 Vue CLI 对 Vue3 的支持明显处于守成状态,长期依赖一个维护不活跃的构建工具,迁移后还是会不断遇到踩坑问题。Vite 本身的冷启动速度足以让开发体验上一个台阶,而且它更早接入了 esbuild,依赖预构建处理对大型依赖包很友好。所以这次我一步到位迁到了 Vite,后面跑起来确实是真香。
升级依赖时还有一个容易踩的坑:如果你原本用vue-cli-service serve,那么.env文件里的变量前缀、路径publicPath等配置都要跟着改。Claude 可以帮你把配置文件从vue.config.js翻译成vite.config.js,但需要注意它对process.env的用法理解有时不完整,关键在于那些自定义环境变量的取值方式。
// vite.config.js 中环境变量示例 export default defineConfig({ base: process.env.VITE_PUBLIC_PATH || '/', server: { port: 3000, proxy: { '/api': { target: process.env.VITE_API_TARGET, changeOrigin: true } } } })3.2 Options API 到 Composition API 的批量改写
这是整个迁移中最耗时、也最有技术含量的部分。传统的做法是一把梭地打开每个文件手动改,这次我改成“Claude 批改 + MiniMax 审阅 + 我抽查”的流水线。
先照体现一下最常见的改写模式。假设有一个 Vue2 计数组件:
// Vue2 Options API export default { data() { return { count: 0, list: [] } }, computed: { doubleCount() { return this.count * 2 } }, watch: { count(newVal, oldVal) { console.log(newVal, oldVal) } }, mounted() { this.fetchList() }, methods: { fetchList() { // 请求数据 } } }Claude 改写后的 Vue3<script setup>版本大致是这样:
<script setup> import { ref, computed, watch, onMounted } from 'vue' const count = ref(0) const list = ref([]) const doubleCount = computed(() => count.value * 2) watch(count, (newVal, oldVal) => { console.log(newVal, oldVal) }) function fetchList() { // 请求数据 } onMounted(() => { fetchList() }) </script>按照这套提示词去批量跑,Claude 的改写成功率大概在八成左右,剩余两成通常集中在复杂组件上,比如逻辑里大量依赖this.$refs、依赖跨组件事件通信,或者存在异步竞态处理。处理这类复杂组件,我会先让 Claude 只改一个文件,然后亲自通读一遍,不急着批量推进。
批处理建议按业务模块切分,不要对整个目录一次丢进去。我试过一次给 50 个文件让 Claude 全量改写,结果跑到一半,模型对某些公共模块的引用就开始混乱,生成的<script setup>里变量名频繁写错。后来改成一次 10 个文件,在每个批次之间让 MiniMax 做个快速校验,准确率立刻上去了。
这里还有个要命的小细节:原组件如果有methods里互相调用的关系,比如methodA调用methodB,改写后必须保持同名函数存在,只是作用域变得更封闭。如果 AI 把某些方法合并或删除,会很难被静态检查发现,只能靠跑页面测出来。这也是我坚持人工抽查的原因。
3.3 路由与状态管理的迁移
路由这块是重灾区,因为 vue-router 3 和 4 的创建方式完全是两套写法。Vue2 是典型的“类实例化”方式,需要在入口文件里用插件注册;Vue3 则是“函数式创建”,直接用createRouter返回实例。
// Vue2 写法 import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ mode: 'history', routes })// Vue3 写法 import { createRouter, createWebHistory } from 'vue-router' export default createRouter({ history: createWebHistory(), routes })路由守卫的写法也变了。Vue2 时代常用beforeEach挂在一个vue-router对象上,或者通过this.$router.beforeEach去注册。Vue3 里所有导航守卫的注册方式都统一成从vue-router导出的方法,这对我这种“守卫里写业务逻辑”的项目来说,迁移成本不算低。建议把守卫逻辑单独抽成一个模块,否则所有入口文件都要改一遍。
状态管理我选的是直接把 Vuex 换成 Pinia。理由很简单:Vuex 4 虽然支持 Vue3,但它的 API 范式还是 Vuex 3 那套 mutation/action 分离,复杂度一点都不低。而 Pinia 天然拥抱 Composition API,store 定义可以写成接近普通业务函数的风格,心智负担小很多。迁移示例:
// Vuex 3 写法 export default new Vuex.Store({ state: { userInfo: {} }, mutations: { SET_USER(state, payload) { state.userInfo = payload } }, actions: { async fetchUser({ commit }) { const res = await request('/user/info') commit('SET_USER', res.data) } } })// Pinia 写法 import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ userInfo: {} }), actions: { async fetchUser() { const res = await request('/user/info') this.userInfo = res.data } } })表面上看只是 API 换了,深层的变化是“mutation 不存在了”。所有对commit('SET_USER', ...)的调用都要改,这对全仓影响面很大。我是靠 Claude 先做一遍全局搜索替换,再让 MiniMax 把剩余的commit调用清单列出来,逐个确认是改掉还是遗漏。整个过程不到一天就全部处理完。
3.4 模板语法和全局 API 的差异处理
如果说 script 部分的迁移是“重振”,那模板部分更像“扫地”,全是零零碎碎的小变动,但漏一个就可能白屏。我在迁移中专门建了一个表,把 Vue2 模板里常见写法与 Vue3 的对应关系列出来,作为 Claude 和 MiniMax 的审阅依据。
| Vue2 写法 | Vue3 中如何处理 | 迁移注意点 |
|---|---|---|
{{ price | formatPrice }} | 过滤器已删除,改为函数调用{{ formatPrice(price) }} | 需要在<script setup>中引入或定义函数 |
组件上v-model默认绑定 value | v-model默认绑定 modelValue,事件名改为 update:modelValue | 自定义组件必须显式声明 modelValue |
.sync修饰符 | 改为v-model:propName写法 | 子组件中 emit 事件名改为 update:propName |
this.$delete(obj, key) | 不再需要,直接delete obj.key(配合 reactive) | Vue3 响应机制自动监听属性删除 |
this.$set(obj, key, val) | 不再需要,直接赋值 | Vue3 的 Proxy 对象天然响应 |
this.$children | 已移除,使用$refs或 provide/inject | 找到实际使用场景逐个替换 |
this.$scopedSlots | 合并到this.$slots,通过函数调用插槽 | 二次封装组件需要特别注意 |
这里最坑的是过滤器。项目里大约十来个地方用了| formatPrice这种写法,Vue3 直接不给过,一编译就报错。好在它的改法非常机械,Claude 处理起来毫不费力。真正费精力的是那些“隐式依赖”,比如有人在computed里用了this.$store.state.user.name,改到 Composition API 后必须显式在 setup 里import { useStore },这种问题不运行到页面根本无法发现,只能靠冒烟测试兜住。
4. 回归验证与异常排查
4.1 构建产物与页面冒烟
迁移完成不是终点,验证才是。我的回归验证分成两层:第一层是构建层面,第二层是页面层面。
构建验证最简单的方式是跑一次vite build,看它能不能无报错产出生产包。但要多留个心眼:Vite 的构建成功不代表代码逻辑正确,它只证明语法和模块引用没大问题。我还会刻意对比迁移前后的构建产物体积和首屏数据,如果出现异常增大,通常说明某个公共模块被重复打包或者动态导入失效了。
页面冒烟测试我采用的是“路由清单法”。把 40 多个路由页面全部列出来,按业务优先级排序,每天下班前由项目组成员跑一遍关键页面,遇到白屏、报错、交互不可用,直接把截图和 console 报错发给我,我统一喂给 Claude 修复。这套流程保证了每天的推进都有反馈闭环,而不是闷头把代码全改完才发现一切是坏的。
4.2 运行时高频报错与修复
四天里我收到了大量运行时错误,高频错误集中在下面这几类。
| 报错信息 | 原因分析 | 修复办法 |
|---|---|---|
| TypeError: this.$scopedSlots is not a function | 模板中仍使用旧插槽 API | 改写为this.$slots.header()形式,或改模板插槽语法 |
| Cannot read properties of undefined(读取 store 的属性) | setup 中没有正确引入 store 实例 | 在<script setup>中用useUserStore()替换this.$store |
[Vue warn]: Property 'xxx' was accessed during render but is not defined | 模板引用了未在 setup 中返回的变量 | 检查是否漏写返回,或引入的 ref 是否拼写不一致 |
| Uncaught TypeError: Cannot read properties of undefined (reading 'push') | 原data里数组未声明或赋错类型 | 检查响应式数据初始化,数组必须用ref([])或reactive({ list: [] }) |
| Element Plus 相关报错:xxx is not a function | 二次封装的 Element UI 组件迁移后方法名变了 | 逐组件对照 Element Plus 文档修改封装层 |
这些错误里,最烦人的是“变量名不一致”的问题。Claude 有时候会把keyword写成keyWord,编译阶段根本发现不了,只有跑到某个搜索页面输入内容时才会炸。解决这类问题只有一个笨办法:让 MiniMax 对每个文件做一次变量名一致性检查,看返回的结果里有没有“疑似赋值未使用”或“可能有变量名差异”的提示。
4.3 没法自动化的边界场景
虽然 AI 辅助大大提升了效率,但也必须承认有些场景没法全自动。我这次迁移中遇到三类边界情况。
第一类是旧依赖不兼容。项目里有内部自己改过的某个图表库,只支持 Vue2,帮不上 Vue3。这类硬骨头只能改造一层适配器,或者换替代方案。AI 可以辅助分析,但决定换还是不换,必须人来拍板。
第二类是组件库升级后的行为差异。Element UI 到 Element Plus,表格组件、表单校验、弹窗组件的 API 都发生了细微变化,这种变化不会立刻报错,但会在具体业务场景里表现为样式错乱或事件回调不触发。我最后是用了一批“兼容混入”临时兜底,再逐步修正的,至少保证迁移上线期间业务不中断。
第三类是模板里的“野路子”写法。有人用三目运算嵌套写复杂的 DOM 逻辑,有人在生命周期钩子里直接操作 DOM。这些代码职责混乱,AI 改写容易出错,我的处理原则是:业务逻辑不动,只做“技术债记账”,标记给后续重构排期。不能顺手改业务,否则迁移冲突会无限扩大,四天根本打不住。
5. 常见问题与避坑实录
5.1 问题速查表
这次迁移中,我和团队最终沉淀了一张“问题速查表”,专门用于迁移过程中的自我检查。分享出来,能省掉很多多研究的时间。
| 问题 | 快速定位方法 | 标准解法 |
|---|---|---|
| 页面白屏且无报错 | F12 看 network 和 console | 检查入口文件是否正确 mount,路由 history 是否配置正确 |
构建报Cannot resolve 'fs'一类 Node 模块 | 查看引用来源,通常是某工具库需要 polyfill | 在 vite.config.js 里配置 define 和 resolve 别名,或替换该库 |
代码迁移后提示define is not defined | 大概率是把 vite 的 define 配置写错了 | 检查环境变量替换写法,@ 别名是否正确 |
| 页面渲染正常但样式全乱 | 检查组件库样式引入方式 | Element Plus 改用按需导入 + unplugin-vue-components |
| 动态路由不生效 | 查看路由守卫里是否还在用next()函数 | Vue Router 4 中守卫必须显式返回 true 或不调用 next |
| 异步组件加载失败 | 控制台出现Failed to fetch dynamically imported module | 检查 import 路径大小写和实际文件名是否完全一致 |
5.2 避坑心得
写到最后,分享几个这四天人肉踩坑换来的心得。
第一个心得:一定要给 AI 设定“不要做什么”。我在提示词里反复强调“保持 props 和 emit 不变”“不做额外重构”,这两个约束让改写乱来的概率大幅下降。没有约束的 AI 改写,经常会顺手把函数顺序调换、重命名变量,虽然它自己觉得很好,但 diff 会巨大无比,人工 review 成本高到难以承受。
第二个心得:检查 AI 生成的代码,重点看 diff,而不是重新看一遍最终文件。如果是全量看文件,人很容易被 AI 写得很规范的代码“催眠”,看不出问题;但如果是看 diff,所有改动集中暴露,一眼就能发现哪些地方动得不对劲。我所有组件抽查都只让 Claude 输出 diff 格式,这一条建议预计可以帮你省下一半审核时间。
第三个心得:扩展阅读相关,如果项目有单测,迁移时保持测试文件及时更新。我这里项目单测覆盖率不高,所以只能靠冒烟;但如果你手上有现成单测,一定要先跑一遍再改,迁移后的绿档回归会给你极大安全感。没有测试的项目,就只能寄希望于严格的人工抽查和运行时验证。
第四个心得:别同时把迁移和版本升级、功能重构混在一起。做 Vue2 到 Vue3 的时候,任何多余的任务都会让 bug 定位变得极其困难。我也想过顺手把几个组件改成 TS 或者优化渲染逻辑,后来都强行忍住了。干净利落地只做技术栈迁移,是控制风险最好的办法。
最后说个比较个人的经验。这四天下来,我对“AI 辅助编码”的理解更具体了:它最大的价值不是把代码写对,而是把“重复劳动”和“人类验证”彻底分开。机器批量流水线式改写,我专注在架构边界和问题排查上,事半功倍。以后再做类似的框架升级,我更倾向于把这种“两个 AI 互相审阅”的模式沉淀成团队的常规流程,毕竟前端框架的迭代不会止于 Vue3。