Vue2 升级 Vue3 要注意啥?从语法到生态一篇讲透
团队项目还在用Vue2,要不要升级Vue3?升级过程要踩哪些坑?从语法到生态,从准备到实战,这篇把升级里的关键问题掰碎了讲,帮你理清楚升级逻辑。
升级Vue3前,得先做哪些准备?
升级不是“头脑一热就开干”,得先摸清项目现状、清理历史包袱、同步团队技能,这三步是“避坑地基”。
-
项目复杂度“摸家底”
要是团队里是几个页面的小项目,直接重构都没压力;但如果是几十个页面、上百个组件的中大型项目,得先画组件依赖图,搞清楚哪些核心组件牵一发动全身,比如电商项目里的购物车、订单组件,升级时得优先评估风险——这些组件一旦出问题,整个业务流程都得崩。 -
先清“技术债”再动手
老项目里可能埋着不少“历史遗留”:比如用了早就不维护的自定义指令,或者依赖的UI库版本太老,像有些项目还在用Vue2专属的vue-awesome-swiper
老版本,升级前得先替换成支持Vue3的swiper-vue
,把项目里的console.log
、无效注释清一清,减少干扰——就像搬家前先扔垃圾,升级时才不会被旧代码绊住。 -
团队技能先“补课”
Vue3的Composition API和Vue2的Options API写法差异大,团队里得有人先吃透新语法,可以组内开“组件重写工作坊”:拿登录组件练手,用setup语法糖重写,对比原来的data
、methods
拆分逻辑,感受代码复用性的提升,比如原来分散在data
和methods
里的表单验证逻辑,用Composition API可以封装成useFormValidate
函数,其他组件直接复用。
Vue2和Vue3核心语法差在哪?这些变化得重点盯
语法是升级的“主战场”,Composition API、响应式原理、模板语法的变化,直接决定代码怎么写。
Composition API 怎么改写老逻辑?
Vue2里用Options API,数据、方法、生命周期是“平铺”的,复杂组件容易变成“面条代码”(逻辑绕来绕去像面条),Vue3的Composition API把逻辑按功能拆分,比如用户信息获取、表单验证,每个功能写成独立的useXXX
函数。
举个🌰:登录组件重构
- Vue2写法(Options API):
export default { data() { return { username: '', password: '' } }, methods: { validate() { /* 验证逻辑 */ }, submit() { /* 提交逻辑 */ } } }
- Vue3写法(Composition API + 语法糖):
export default { setup() { const username = ref('') const password = ref('') const validate = () => { /* 验证逻辑 */ } const submit = () => { /* 提交逻辑 */ } return { username, password, validate, submit } } } // 或者更灵活:把逻辑封装到useLogin函数里 function useLogin() { const username = ref('') const password = ref('') // ... 验证、提交逻辑 return { username, password, validate, submit } } export default { setup() { return useLogin() } }
Composition API让逻辑复用更丝滑,不像Mixin容易出现命名冲突(比如两个Mixin都定义了
handleClick
方法,就会打架)。
响应式原理变了,写代码要注意啥?
Vue2用Object.defineProperty
,对数组下标修改、对象新增属性不响应,所以得用this.$set
,Vue3换成Proxy
,能直接监听这些操作了!但升级时得注意:
-
老代码里的
$set
要逐步替换,比如原来给对象加新属性:this.$set(user, 'age', 18)
,Vue3里直接user.age = 18
就有响应式。 -
如果是第三方库返回的非响应式对象(比如后端接口返回的用户信息),得用
reactive
或ref
包一下,不然修改不触发更新。// 错误:直接赋值非响应式对象 const user = { name: '张三' } user.age = 18 // 页面不更新 // 正确:用reactive包裹 const user = reactive({ name: '张三' }) user.age = 18 // 页面自动更新
模板语法新增了哪些“好用货”?
Vue3的模板语法加了不少“偷懒神器”,解决了Vue2的痛点:
-
多根节点支持:Vue2里组件模板必须只有一个根元素,现在可以直接写多个div并列,不用再套个无意义的div。
<!-- Vue2 必须套div --> <template> <div> <h1>标题</h1> <p>内容</p> </div> </template> <!-- Vue3 直接写多根 --> <template> <h1>标题</h1> <p>内容</p> </template>
-
Teleport(传送门):能把组件内容“传送”到body或其他指定DOM节点,做弹窗、下拉菜单特别方便,比如做全局提示组件,原来得在根组件里写,现在用
把提示框直接挂到body,避免样式被父级溢出隐藏影响:<template> <Teleport to="body"> <div class="modal">这是弹窗</div> </Teleport> </template>
-
Suspense(实验性):实现异步组件加载时的占位 fallback,提升用户体验,比如加载一个耗时的表格组件,在组件加载完成前显示“加载中”:
<template> <Suspense> <template #default> <AsyncTable /> <!-- 异步组件 --> </template> <template #fallback> <div>加载中...</div> </template> </Suspense> </template>
哪些API彻底不能用了?
Vue3“砍掉”了一些过时API,升级时得逐个替换:
-
事件总线($on/$off):Vue2里常用
this.$on
、this.$off
实现跨组件通信,Vue3官方不再支持,得用mitt
这类库替代。 -
Filter(过滤器):原来用
{{ message | uppercase }}
格式化文本,现在得用计算属性或者方法代替,比如写个toUppercase
方法:const toUppercase = (str) => str.toUpperCase()
模板里用
{{ toUppercase(message) }}
。 -
$children、$listeners:这些API被移除,要改用
refs
获取子组件实例,或用provide/inject
做跨层级通信。
生态工具链升级,UI库、路由、构建工具咋换?
升级不是只改Vue语法,UI库、路由、构建工具这些“周边生态”也得同步适配。
UI组件库:Element UI → Element Plus 有啥坑?
Element UI只支持Vue2,升级得换Element Plus,光组件命名风格的变化,就能让你改到“眼酸”——所有组件名从kebab-case(短横线分隔)变成PascalCase(大驼峰),比如原来的
要改成,
变成。
图标系统也独立了,得额外安装@element-plus/icons-vue
,用的时候要引入具体图标组件,比如
得写成:
<template> <ElIcon><Search /></ElIcon> </template> <script setup> import { ElIcon, Search } from '@element-plus/icons-vue' </script>
样式引入也有暗坑:Vue2里引入Element UI样式是import 'element-ui/lib/theme-chalk/index.css'
,Vue3的Element Plus得改成import 'element-plus/dist/index.css'
,要是路径写错,整个组件库样式会“失踪”,按钮变成白板。
还有组件逻辑调整,比如Tree组件的懒加载,Vue2里用lazy
属性+load
方法,Vue3里得配合useLazyLoad
钩子,参数传递方式完全不同,必须对照官方迁移文档一行行改。
路由和状态管理:Vue Router、Vuex 咋升级?
Vue Router从3.x跳到4.x,最直观的是创建路由的方式:
-
Vue2写法:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const router = new VueRouter({ routes: [/* 路由配置 */] }) export default router
-
Vue3写法:
import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), // 替代原来的mode: 'history' routes: [/* 路由配置 */] }) export default router
路由跳转的语法,虽然this.$router.push('/home')
还能临时用,但Vue3推荐用useRouter
hooks:
import { useRouter } from 'vue-router' const router = useRouter() router.push('/home')
Vuex的升级相对温和,Vuex 4.x对Vue3做了兼容,但社区更推荐Pinia(由Vuex核心团队开发),Pinia完全抛弃了Vuex的modules
、mutations
这些“繁文缛节”,用更简洁的函数式写法:
-
Vuex定义模块:
const userModule = { state: () => ({ name: '' }), mutations: { setName(state, name) { state.name = name } } }
-
Pinia定义store:
import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ name: '' }), actions: { setName(name) { this.name = name } } // 没有mutations,直接在actions里改状态 })
Pinia天生支持Composition API,在组件里用
const userStore = useUserStore()
就能拿到响应式状态,比Vuex的mapState
、mapActions
清爽太多。
构建工具:Vue CLI 转 Vite 能提速多少?
Vue2项目用Vue CLI(基于Webpack)启动时,得等Webpack把所有依赖打包,冷启动可能要几十秒;换成Vite后,利用ESModule的特性,冷启动秒级完成,开发时修改代码也是“秒更”,但迁移不是“一键替换”,得处理这些差异:
-
配置文件:Vue CLI用
vue.config.js
,Vite用vite.config.js
,比如配置别名,Vue CLI里是:chainWebpack: config => { config.resolve.alias.set('@', path.resolve(__dirname, 'src')) }
Vite里直接写:
resolve: { alias: { '@': path.resolve(__dirname, 'src') } }
-
依赖兼容:Vite默认只认ESModule格式的包,有些老库是CommonJS写的(比如某些日期处理库),得装
vite-plugin-commonjs
插件转换,要是项目里用了require('xxx')
语法,也得改成import
。 -
环境变量:Vue CLI里的
process.env.VUE_APP_XXX
,Vite里要改成import.meta.env.VITEXXX
,还要在.env
文件里把前缀改成VITE
。
如果团队暂时不想换构建工具,Vue CLI也能支持Vue3——把@vue/cli-service
升级到5.x版本,vue-loader
升到16+,再把项目里的Vue版本改成3.x,就能在Webpack环境下跑Vue3代码。
实战升级步骤,怎么降低风险?
升级是个“技术活”,得用“小步快跑、逐步验证”的策略,别搞“大爆炸式重构”。
先拿“小模块”练手
别直接动核心页面,先挑独立的小组件,比如弹窗、按钮组件,比如把项目里的Confirm组件用Vue3重写,用setup语法糖,把原来的data
、methods
改成ref
、function
,测试响应式和事件逻辑是否正常,跑通一个组件后,再逐步替换列表、表单这些复杂组件。
举个🌰:重构Button组件
原来Vue2的Button组件可能长这样:
export default { props: { type: String }, data() { return { loading: false } }, methods: { handleClick() { this.$emit('click') } } }
Vue3重写后(用语法糖):
<script setup> const props = defineProps({ type: String }) const emit = defineEmits(['click']) const loading = ref(false) const handleClick = () => emit('click') </script>
改完后,在测试页面里反复点击、传参,确认功能和样式都没崩,再推进下一个组件。
依赖版本锁死,避免“版本乱战”
升级时package.json
里的依赖要逐个检查:vue升级到3.x,vue-loader要16+,vue-router4.x,vuex4.x(或Pinia),升级后先npm install
,看有没有依赖冲突,用npm ls
查依赖树。
比如曾经有项目升级时,eslint-plugin-vue
版本没同步,导致代码检查报错,后来把eslint-plugin-vue
升到9.x才解决,所以升级后一定要锁定依赖版本,避免不同依赖之间“打架”。
测试环节必须“武装到牙齿”
升级后功能有没有崩?样式有没有乱?得靠测试把把关:
-
单元测试:用Vue Test Utils 2.x,原来的
mount
、shallowMount
用法有变化,比如现在要导入from '@vue/test-utils'
,比如测试Button组件的点击事件:import { mount } from '@vue/test-utils' import MyButton from './MyButton.vue' test('button click emits event', () => { const wrapper = mount(MyButton) wrapper.trigger('click') expect(wrapper.emitted('click')).toBeTruthy() })
-
E2E测试:用Cypress或Playwright,把核心流程(比如登录、下单)跑一遍,确保升级后功能没断,还可以加可视化diff工具(比如Argos),对比升级前后页面渲染是否一致,防止样式崩掉。
性能监控:升级后真的变快了吗?
Vue3的Tree-shaking能把没用的代码删掉,包体积通常能减小30%以上,用webpack-bundle-analyzer
(Vue CLI项目)或vite-plugin-bundle-visualizer
(Vite项目)看打包后的chunk大小,对比升级前后。
运行时性能方面,用Chrome DevTools的Performance面板,记录组件渲染时间,Vue3的响应式追踪更高效,复杂列表的渲染速度应该有提升,要是发现某些组件反而变慢,可能是reactive
用多了(reactive
对对象深度监听,性能开销比ref
大),换成ref
更轻量。
这些“大坑”升级时千万避开!
升级路上总有意外,这几个高频“暗礁”得提前防住。
第三方库突然“不认识”Vue3了
有些老库只支持Vue2,比如某些图表库、地图SDK,这时候要么找替代库(比如ECharts有官方Vue3组件),要么自己封装适配层:把Vue3的组件用defineComponent
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。