ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue 2 到 Vue 3 项目升级实战:从评估到部署的完整清单

Vue 2 到 Vue 3 项目升级实战:从评估到部署的完整清单

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 vuexyarn why vue vue-router vuex来查看它们确切的版本和依赖关系。

接下来,制作一个依赖兼容性矩阵表。Vue 3 的核心变化导致许多流行库需要升级到特定版本才能兼容。你需要逐一核对。以下是我在升级时整理的核心清单,你的项目可能还涉及其他库(如 UI 库、图表库等),需要单独调研。

库名称Vue 2 典型版本Vue 3 兼容版本升级关键点
Vue2.6.x3.2.x 或更高核心对象,必须升级
Vue Router3.x4.xAPI 基本一致,但创建方式、部分钩子名变更
Vuex3.x4.xAPI 基本一致,创建方式变更,须用createStore
Vue 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,你的代码里可能藏着这些“地雷”。

  1. 使用官方迁移构建模式:Vue 官方提供了一个@vue/compat包,它允许你在 Vue 3 环境中以“兼容模式”运行 Vue 2 代码,并会在控制台发出警告,指出哪些写法需要修改。这是最有效的发现工具。你可以先创建一个临时分支,安装@vue/compat并按照指南配置,然后运行项目,查看控制台输出的所有警告和错误,逐一记录。
  2. 重点扫描清单
    • 过滤器 (Filters):Vue 3 已移除。全局过滤器需要改用全局方法或计算属性;局部过滤器需改为组件内的方法或计算属性。
    • 事件 API ($on,$off,$once):已移除。依赖事件总线的代码需要重构,推荐使用mitttiny-emitter这类第三方库替代。
    • 按键修饰符keyCode支持已移除。需要将类似v-on:keyup.13改为v-on:keyup.enter
    • $children$listeners:已移除。访问子组件推荐使用ref$attrs
    • 生命周期钩子destroyed应改为unmountedbeforeDestroy应改为beforeUnmount
  3. 构建工具评估:如果你的项目使用 Vue CLI,升级到 Vue 3 后可以继续使用 Vue CLI(需升级到 v5),但更推荐借此机会迁移到Vite。Vite 的启动速度和热更新速度有数量级的提升,能极大改善开发体验。评估一下项目对 Webpack 特定插件或配置的依赖程度,规划迁移成本。

3. 分步升级实施清单

准备工作做完,手里有了一份“问题清单”,现在可以开始按步骤实施了。我建议在一个独立的功能分支上进行,并频繁提交,便于回滚。

3.1 第一步:更新 package.json 与依赖安装

这是最直接的一步,但需要小心依赖冲突。

  1. 修改版本号:在package.json中,将vue的版本更新为^3.2.0或更高稳定版。同时,根据之前的兼容性矩阵,更新vue-router^4.0.0vuex^4.0.0
  2. 处理 UI 库:以 Element UI 为例,你需要卸载旧的,安装新的。
    npm uninstall element-ui npm install element-plus # 同时,你可能需要安装按需导入的插件 npm install -D unplugin-vue-components unplugin-auto-import
  3. 清理并安装:删除node_modulespackage-lock.json(或yarn.lock),然后运行npm installyarn 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属性,改为methodscomputed
    // 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。在父组件模板中给子组件添加ref="childRef",然后在脚本中通过this.$refs.childRef访问。
  • $listeners:在 Vue 3 中,$attrs包含了传递给组件的所有属性和事件监听器(除了classstyle)。如果你之前在组件内手动处理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 router

Vuex 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,这一步可以显著提升开发幸福感。

  1. 安装 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
  2. 创建vite.config.js

    import { 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 // 自动打开浏览器 } })
  3. 修改index.html与入口

    • public/index.html移动到项目根目录。
    • index.html中,删除所有关于%=的 Webpack 模板变量,并通过<script type="module" src="/src/main.js"></script>直接引入入口文件。
    • 确保src/main.js的导入路径正确。
  4. 更新package.json脚本

    { "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" } }
  5. 处理环境变量:Vite 使用import.meta.env替代process.env。你需要将代码中所有的process.env替换为import.meta.env,并且环境变量前缀从VUE_APP_改为VITE_

4. 升级后验证与测试

代码修改完成后,绝不能直接部署。必须经过严格的验证。

4.1 基础功能冒烟测试

  1. 启动开发服务器:运行npm run dev,确保项目能成功启动,没有白屏和明显的运行时错误。
  2. 核心流程走查:手动测试最关键的用户路径,例如:首页加载、用户登录、主要数据列表查看、表单提交、页面跳转等。确保基本交互正常。
  3. 浏览器控制台检查:打开开发者工具,查看 Console 和 Network 面板,确保没有未处理的警告和 404 请求(特别是静态资源路径在 Vite 下可能变化)。

4.2 单元测试与端到端测试修复

如果你的项目有测试用例(这非常棒!),现在它们大概率会全部失败。

  • 单元测试 (如 Jest + Vue Test Utils):Vue Test Utils 从 v1 升级到 v2,API 有破坏性变更。最常见的是:
    • mountshallowMount的返回值变了,访问 wrapper.vm 的方式可能不同。
    • findfindAll的选择器语法可能更严格。
    • 需要更新测试工具配置以支持 Vue 3。你需要参照 Vue Test Utils v2 的迁移指南,逐个修复测试文件。这是一个细致活,但能确保你的组件逻辑在升级后依然正确。
  • 端到端测试 (如 Cypress):通常影响较小,只要页面元素和交互流程没变,测试用例只需重新运行即可。但需注意如果组件类名或结构因 UI 库更换而改变,需要更新选择器。

4.3 性能与打包分析

  1. 构建分析:运行npm run build,观察打包过程是否有错误或警告。对比升级前后的打包体积(查看dist文件夹大小,或使用rollup-plugin-visualizer生成分析报告)。由于 Vue 3 本身更轻量,以及 Vite 的优化,通常打包体积会有所减少。
  2. 运行时性能:在浏览器开发者工具的 Performance 面板中,记录关键操作(如页面切换、大数据列表渲染),与升级前进行粗略对比,确保没有明显的性能回退。

5. 常见问题与排查实录

在升级过程中,我遇到了不少“坑”,这里记录下最典型的几个及其解决方案。

5.1 第三方库控制台警告 “Component missing template or render function”

问题描述:启动项目后,控制台大量警告,提示某个组件缺少模板或渲染函数,但页面似乎又能正常显示一部分。根本原因:这是 UI 库组件按需导入配置不正确导致的。在 Vite 中,使用unplugin-vue-components自动导入组件时,该插件会在编译时动态解析并注册组件。如果配置有误或组件名写错,Vue 在运行时找不到对应的组件定义,就会抛出此警告。解决方案

  1. 检查vite.config.jsComponents插件的resolvers配置是否正确指向了你使用的 UI 库(如ElementPlusResolver())。
  2. 确保在模板中使用的组件名与 UI 库导出的名称完全一致(注意大小写)。
  3. 对于某些无法被自动导入器识别的特殊组件,考虑在局部手动导入并注册。
  4. 可以暂时在main.js中全局导入整个 UI 库以确认是否是按需导入的问题:app.use(ElementPlus)。如果警告消失,则问题肯定出在按需导入配置上。

5.2 项目中使用了 Vue.extend 定义的组件无法渲染

问题描述:一些使用Vue.extend({ ... })定义的传统组件在升级后渲染空白或报错。根本原因:Vue 3 中defineComponent是 TypeScript 友好且推荐的方式,但Vue.extend在多数情况下仍能工作。问题可能出在组件内部使用了已被移除的 Vue 2 API(如过滤器),或者混入 (mixin) 中存在兼容性问题。解决方案

  1. 优先将Vue.extend改为defineComponent,这是一个简单的替换,通常能解决大部分问题。
    // 之前 import Vue from 'vue' export default Vue.extend({ ... }) // 之后 import { defineComponent } from 'vue' export default defineComponent({ ... })
  2. 检查该组件及其混入的代码,确保没有使用过滤器、$on/$off等已移除的 API。
  3. 如果组件逻辑复杂,考虑将其重构为 Composition API setup 函数,这能更好地利用 Vue 3 的新特性。

5.3 样式丢失或布局错乱

问题描述:升级 UI 库(如 Element UI -> Element Plus)后,页面样式变得混乱,组件间距、颜色、字体大小都不对。根本原因:UI 库的 CSS 样式名称、CSS 变量或默认样式可能发生了改变。此外,从 Webpack 迁移到 Vite 后,CSS 预处理器的全局变量、混入文件导入路径可能失效。解决方案

  1. 检查全局样式:确保正确引入了新 UI 库的样式文件。对于 Element Plus,可能需要手动引入index.css或在插件解析器中配置导入样式。
    // vite.config.js - Components 插件配置 Components({ resolvers: [ ElementPlusResolver({ importStyle: 'css', // 确保导入样式 }), ], }),
  2. 核对自定义覆盖样式:你之前可能通过更高优先级的选择器覆盖了原 UI 库样式。新库的 CSS 类名可能已变,导致你的覆盖样式失效。需要打开浏览器检查器,找到目标元素的新类名,并更新你的自定义 CSS。
  3. 检查 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的方式已改变。或者,在组合式函数中使用了错误的导入方式。解决方案
  1. 路由:确保路由实例是通过app.use(router)正确安装的。检查导航守卫,将next()调用改为返回trueundefined(表示通过),返回false表示取消,返回一个路由路径对象进行重定向。
    // Vue Router 4 导航守卫 router.beforeEach((to, from) => { // 返回 false 取消导航 // return false // 返回 undefined 或 true 继续 // return true // 重定向 // return { path: '/login' } })
  2. 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 带来的开发体验提升。

返回列表