1. 项目概述与升级动因最近在维护一个老项目技术栈是Vue 2.x Webpack 4随着项目迭代和团队新成员的加入Vue 2.x 的一些局限性开始显现比如 Composition API 的缺失让复杂逻辑复用变得困难TypeScript 的支持也不够原生和友好。更重要的是Vue 2 将在 2024 年底进入生命周期结束阶段这意味着官方将不再提供新功能和安全更新。因此将项目从 Vue 2 平稳升级到 Vue 3从一个“技术债”变成了一个必须提上日程的“技术投资”。这个升级过程远不止是改个版本号那么简单它涉及到核心 API 的变更、生态库的适配、构建工具的升级以及团队开发习惯的转变。我花了大约两周时间完成了从评估、实施到验证的全过程过程中积累了一份非常详细的“修改清单”。这份清单不是简单的命令罗列而是包含了每个改动背后的原因、可能遇到的坑以及具体的解决方案希望能给面临同样升级任务的你提供一个清晰的路线图。2. 升级前准备与评估在动手写第一行代码之前充分的准备工作是决定升级成败的关键。盲目升级只会导致项目在某个环节卡住进退两难。2.1 环境与依赖全面审计首先你需要对现有项目进行一次彻底的“体检”。打开package.json这是你的起点。除了记录 Vue 版本通常是vue^2.6.14更要重点关注与 Vue 强相关的生态库。使用命令npm list vue vue-router vuex或yarn why vue vue-router vuex来查看它们确切的版本和依赖关系。接下来制作一个依赖兼容性矩阵表。Vue 3 的核心变化导致许多流行库需要升级到特定版本才能兼容。你需要逐一核对。以下是我在升级时整理的核心清单你的项目可能还涉及其他库如 UI 库、图表库等需要单独调研。库名称Vue 2 典型版本Vue 3 兼容版本升级关键点Vue2.6.x3.2.x 或更高核心对象必须升级Vue Router3.x4.xAPI 基本一致但创建方式、部分钩子名变更Vuex3.x4.xAPI 基本一致创建方式变更须用createStoreVue CLI / ViteVue CLI 4/5Vite 推荐构建工具建议迁移至 Vite 以获得最佳体验Element UI2.xElement Plus1.x 或更高不是升级是替换为全新的 Element Plus 库Vuetify2.x3.x有官方升级指南但变动较大需仔细评估Vue-i18n8.x9.xAPI 有重大变化需迁移Vue Test Utils1.x2.x测试 API 变化很大测试用例需要重写或调整注意对于大型 UI 库如 Element UI、Ant Design Vue务必查阅其官方提供的 Vue 3 迁移指南或版本。它们通常不是简单升级而是提供了一个全新的 Vue 3 兼容版本如 Element Plus这意味着你需要修改大量组件导入和部分 API 调用。2.2 代码库健康度检查依赖理清后就要审视自己的代码了。Vue 3 移除或改变了部分 Vue 2 API你的代码里可能藏着这些“地雷”。使用官方迁移构建模式Vue 官方提供了一个vue/compat包它允许你在 Vue 3 环境中以“兼容模式”运行 Vue 2 代码并会在控制台发出警告指出哪些写法需要修改。这是最有效的发现工具。你可以先创建一个临时分支安装vue/compat并按照指南配置然后运行项目查看控制台输出的所有警告和错误逐一记录。重点扫描清单过滤器 (Filters)Vue 3 已移除。全局过滤器需要改用全局方法或计算属性局部过滤器需改为组件内的方法或计算属性。事件 API ($on,$off,$once)已移除。依赖事件总线的代码需要重构推荐使用mitt或tiny-emitter这类第三方库替代。按键修饰符keyCode支持已移除。需要将类似v-on:keyup.13改为v-on:keyup.enter。$children和$listeners已移除。访问子组件推荐使用ref和$attrs。生命周期钩子destroyed应改为unmountedbeforeDestroy应改为beforeUnmount。构建工具评估如果你的项目使用 Vue CLI升级到 Vue 3 后可以继续使用 Vue CLI需升级到 v5但更推荐借此机会迁移到Vite。Vite 的启动速度和热更新速度有数量级的提升能极大改善开发体验。评估一下项目对 Webpack 特定插件或配置的依赖程度规划迁移成本。3. 分步升级实施清单准备工作做完手里有了一份“问题清单”现在可以开始按步骤实施了。我建议在一个独立的功能分支上进行并频繁提交便于回滚。3.1 第一步更新 package.json 与依赖安装这是最直接的一步但需要小心依赖冲突。修改版本号在package.json中将vue的版本更新为^3.2.0或更高稳定版。同时根据之前的兼容性矩阵更新vue-router到^4.0.0vuex到^4.0.0。处理 UI 库以 Element UI 为例你需要卸载旧的安装新的。npm uninstall element-ui npm install element-plus # 同时你可能需要安装按需导入的插件 npm install -D unplugin-vue-components unplugin-auto-import清理并安装删除node_modules和package-lock.json或yarn.lock然后运行npm install或yarn install重新安装所有依赖。这一步可能会报错提示某些包不兼容需要你根据错误信息进一步调整版本或寻找替代包。3.2 第二步修改入口文件与 Vue 实例创建Vue 3 的初始化方式从“构造函数”变成了“工厂函数”这是第一个需要适应的代码改动点。Vue 2 的写法 (src/main.js):import Vue from vue import App from ./App.vue import router from ./router import store from ./store Vue.config.productionTip false new Vue({ router, store, render: h h(App) }).$mount(#app)Vue 3 的写法 (src/main.js):import { createApp } from vue // 注意是从 vue 导入 createApp import App from ./App.vue import router from ./router import store from ./store // 不再需要 Vue.config.productionTip const app createApp(App) // 创建应用实例 // 使用 use 方法注册插件 app.use(router) app.use(store) // 挂载 app.mount(#app)关键变化解析createApp是一个函数调用它返回一个应用实例app。全局配置如Vue.config.xxx现在通过应用实例app.config进行设置。插件Router, Store, i18n等不再自动注入需要通过app.use()显式安装。全局组件、指令、混入 (mixin) 的注册也改为通过app.component(),app.directive(),app.mixin()方法。3.3 第三步逐项解决 API 与语法变更按照之前“代码健康检查”列出的清单开始批量修改源代码。这是最耗时但也最核心的一步。1. 过滤器迁移 查找所有|管道符的使用。全局过滤器在main.js中改为注册全局方法或使用插件。// Vue 2: Vue.filter(currency, ...) // Vue 3: app.config.globalProperties.$filters { currency(value) { /* ... */ } } // 模板中使用{{ $filters.currency(price) }}局部过滤器在组件选项中移除filters属性改为methods或computed。// Vue 2: filters: { currency(value) {...} } // Vue 3: methods: { currency(value) { /* ... */ } } // 模板中使用{{ currency(price) }}2. 事件总线重构 如果项目使用了new Vue()作为事件总线需要替换。安装mitt:npm install mitt创建一个事件总线工具文件如src/utils/eventBus.js:import mitt from mitt const emitter mitt() export default emitter在需要的地方导入并使用import emitter from /utils/eventBus // 发送事件 emitter.emit(some-event, data) // 监听事件 emitter.on(some-event, (data) { ... }) // 移除监听 emitter.off(some-event, handler)3. 生命周期钩子重命名 使用编辑器的全局搜索替换功能将beforeDestroy替换为beforeUnmount将destroyed替换为unmounted。注意选项式 API 和 Composition API 中名称一致。4.$children和$listeners移除$children如果需要访问子组件实例应该使用ref。在父组件模板中给子组件添加refchildRef然后在脚本中通过this.$refs.childRef访问。$listeners在 Vue 3 中$attrs包含了传递给组件的所有属性和事件监听器除了class和style。如果你之前在组件内手动处理v-on$listeners现在需要改为v-bind$attrs实际上在 Vue 3 的组件中未声明的属性和事件监听器会自动继承到根元素除非设置inheritAttrs: false。3.4 第四步Vue Router 与 Vuex 升级调整这两个官方库的 API 在 Vue 3 中变化相对较小但创建方式必须更新。Vue Router 4创建方式从new VueRouter()变为createRouter()。模式定义从mode: history变为history: createWebHistory()。router.push()的next参数在导航守卫中被移除现在守卫函数返回false表示取消导航。src/router/index.js修改示例import { createRouter, createWebHistory } from vue-router // 注意导入 import routes from ./routes // 你的路由表 const router createRouter({ history: createWebHistory(process.env.BASE_URL), // 替代 mode routes }) export default routerVuex 4创建方式从new Vuex.Store()变为createStore()。其他核心概念state, getters, mutations, actions, modules用法基本不变。src/store/index.js修改示例import { createStore } from vuex // 注意导入 export default createStore({ state: { ... }, mutations: { ... }, actions: { ... }, modules: { ... } })3.5 第五步构建工具迁移可选但推荐如果你决定从 Vue CLI (Webpack) 迁移到 Vite这一步可以显著提升开发幸福感。安装 Vite 及相关插件npm install -D vite vitejs/plugin-vue # 如果使用 Vue Router 4 和 Vuex 4它们已支持 Vite无需额外插件 # 如果使用 Element Plus安装对应的按需导入插件 npm install -D unplugin-vue-components unplugin-auto-import创建vite.config.jsimport { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), // Element Plus 按需导入 AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], resolve: { alias: { : resolve(__dirname, src) // 保持与 Webpack 相同的别名 } }, server: { port: 8080, // 指定开发服务器端口 open: true // 自动打开浏览器 } })修改index.html与入口将public/index.html移动到项目根目录。在index.html中删除所有关于%的 Webpack 模板变量并通过script typemodule src/src/main.js/script直接引入入口文件。确保src/main.js的导入路径正确。更新package.json脚本{ scripts: { dev: vite, build: vite build, preview: vite preview } }处理环境变量Vite 使用import.meta.env替代process.env。你需要将代码中所有的process.env替换为import.meta.env并且环境变量前缀从VUE_APP_改为VITE_。4. 升级后验证与测试代码修改完成后绝不能直接部署。必须经过严格的验证。4.1 基础功能冒烟测试启动开发服务器运行npm run dev确保项目能成功启动没有白屏和明显的运行时错误。核心流程走查手动测试最关键的用户路径例如首页加载、用户登录、主要数据列表查看、表单提交、页面跳转等。确保基本交互正常。浏览器控制台检查打开开发者工具查看 Console 和 Network 面板确保没有未处理的警告和 404 请求特别是静态资源路径在 Vite 下可能变化。4.2 单元测试与端到端测试修复如果你的项目有测试用例这非常棒现在它们大概率会全部失败。单元测试 (如 Jest Vue Test Utils)Vue Test Utils 从 v1 升级到 v2API 有破坏性变更。最常见的是mount和shallowMount的返回值变了访问 wrapper.vm 的方式可能不同。find和findAll的选择器语法可能更严格。需要更新测试工具配置以支持 Vue 3。你需要参照 Vue Test Utils v2 的迁移指南逐个修复测试文件。这是一个细致活但能确保你的组件逻辑在升级后依然正确。端到端测试 (如 Cypress)通常影响较小只要页面元素和交互流程没变测试用例只需重新运行即可。但需注意如果组件类名或结构因 UI 库更换而改变需要更新选择器。4.3 性能与打包分析构建分析运行npm run build观察打包过程是否有错误或警告。对比升级前后的打包体积查看dist文件夹大小或使用rollup-plugin-visualizer生成分析报告。由于 Vue 3 本身更轻量以及 Vite 的优化通常打包体积会有所减少。运行时性能在浏览器开发者工具的 Performance 面板中记录关键操作如页面切换、大数据列表渲染与升级前进行粗略对比确保没有明显的性能回退。5. 常见问题与排查实录在升级过程中我遇到了不少“坑”这里记录下最典型的几个及其解决方案。5.1 第三方库控制台警告 “Component missing template or render function”问题描述启动项目后控制台大量警告提示某个组件缺少模板或渲染函数但页面似乎又能正常显示一部分。根本原因这是 UI 库组件按需导入配置不正确导致的。在 Vite 中使用unplugin-vue-components自动导入组件时该插件会在编译时动态解析并注册组件。如果配置有误或组件名写错Vue 在运行时找不到对应的组件定义就会抛出此警告。解决方案检查vite.config.js中Components插件的resolvers配置是否正确指向了你使用的 UI 库如ElementPlusResolver()。确保在模板中使用的组件名与 UI 库导出的名称完全一致注意大小写。对于某些无法被自动导入器识别的特殊组件考虑在局部手动导入并注册。可以暂时在main.js中全局导入整个 UI 库以确认是否是按需导入的问题app.use(ElementPlus)。如果警告消失则问题肯定出在按需导入配置上。5.2 项目中使用了 Vue.extend 定义的组件无法渲染问题描述一些使用Vue.extend({ ... })定义的传统组件在升级后渲染空白或报错。根本原因Vue 3 中defineComponent是 TypeScript 友好且推荐的方式但Vue.extend在多数情况下仍能工作。问题可能出在组件内部使用了已被移除的 Vue 2 API如过滤器或者混入 (mixin) 中存在兼容性问题。解决方案优先将Vue.extend改为defineComponent这是一个简单的替换通常能解决大部分问题。// 之前 import Vue from vue export default Vue.extend({ ... }) // 之后 import { defineComponent } from vue export default defineComponent({ ... })检查该组件及其混入的代码确保没有使用过滤器、$on/$off等已移除的 API。如果组件逻辑复杂考虑将其重构为 Composition API setup 函数这能更好地利用 Vue 3 的新特性。5.3 样式丢失或布局错乱问题描述升级 UI 库如 Element UI - Element Plus后页面样式变得混乱组件间距、颜色、字体大小都不对。根本原因UI 库的 CSS 样式名称、CSS 变量或默认样式可能发生了改变。此外从 Webpack 迁移到 Vite 后CSS 预处理器的全局变量、混入文件导入路径可能失效。解决方案检查全局样式确保正确引入了新 UI 库的样式文件。对于 Element Plus可能需要手动引入index.css或在插件解析器中配置导入样式。// vite.config.js - Components 插件配置 Components({ resolvers: [ ElementPlusResolver({ importStyle: css, // 确保导入样式 }), ], }),核对自定义覆盖样式你之前可能通过更高优先级的选择器覆盖了原 UI 库样式。新库的 CSS 类名可能已变导致你的覆盖样式失效。需要打开浏览器检查器找到目标元素的新类名并更新你的自定义 CSS。检查 CSS 预处理器配置在vite.config.js中可能需要重新配置css.preprocessorOptions来引入全局的 SCSS/Less 变量文件。export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: import /styles/variables.scss; // 全局变量 } } } })5.4 路由跳转或状态管理相关错误问题描述点击路由链接无反应或页面刷新后 Vuex 状态丢失。根本原因路由Vue Router 4 的初始化方式或导航守卫用法有误。例如在守卫中使用了已被移除的next参数。状态管理在 Vue 3 的 setup 函数中访问this.$store的方式已改变。或者在组合式函数中使用了错误的导入方式。解决方案路由确保路由实例是通过app.use(router)正确安装的。检查导航守卫将next()调用改为返回true或undefined表示通过返回false表示取消返回一个路由路径对象进行重定向。// Vue Router 4 导航守卫 router.beforeEach((to, from) { // 返回 false 取消导航 // return false // 返回 undefined 或 true 继续 // return true // 重定向 // return { path: /login } })Vuex在组合式 API 中使用useStore钩子来获取 store 实例。import { useStore } from vuex export default { setup() { const store useStore() // 现在可以使用 store.state, store.commit, store.dispatch return {} } }在选项式 API 中仍然可以通过this.$store访问前提是 store 已通过app.use(store)正确安装。整个升级过程就像给一架正在飞行的飞机更换引擎需要缜密的计划、细致的操作和充分的测试。我的体会是不要试图在一天内完成所有工作。建立一个清晰的清单分模块、分步骤进行每完成一个模块就进行验证。充分利用vue/compat构建模式来发现潜在问题它能在早期为你节省大量调试时间。最后升级不仅是技术栈的更新更是团队拥抱更现代、更高效开发模式的机会尤其是 Composition API 的引入为复杂逻辑的组织和复用打开了新的大门。在升级完成后可以鼓励团队在新功能开发中尝试使用script setup语法和组合式函数逐步享受 Vue 3 带来的开发体验提升。