Code前端首页关于Code前端联系我们

Vue2 升级 Vue3 要注意啥?从语法到生态一篇讲透

terry 5小时前 阅读数 8 #Vue
文章标签 Vue2升级;Vue3

团队项目还在用Vue2,要不要升级Vue3?升级过程要踩哪些坑?从语法到生态,从准备到实战,这篇把升级里的关键问题掰碎了讲,帮你理清楚升级逻辑。

升级Vue3前,得先做哪些准备?

升级不是“头脑一热就开干”,得先摸清项目现状、清理历史包袱、同步团队技能,这三步是“避坑地基”。

  1. 项目复杂度“摸家底”
    要是团队里是几个页面的小项目,直接重构都没压力;但如果是几十个页面、上百个组件的中大型项目,得先画组件依赖图,搞清楚哪些核心组件牵一发动全身,比如电商项目里的购物车、订单组件,升级时得优先评估风险——这些组件一旦出问题,整个业务流程都得崩。

  2. 先清“技术债”再动手
    老项目里可能埋着不少“历史遗留”:比如用了早就不维护的自定义指令,或者依赖的UI库版本太老,像有些项目还在用Vue2专属的vue-awesome-swiper老版本,升级前得先替换成支持Vue3的swiper-vue,把项目里的console.log、无效注释清一清,减少干扰——就像搬家前先扔垃圾,升级时才不会被旧代码绊住。

  3. 团队技能先“补课”
    Vue3的Composition API和Vue2的Options API写法差异大,团队里得有人先吃透新语法,可以组内开“组件重写工作坊”:拿登录组件练手,用setup语法糖重写,对比原来的datamethods拆分逻辑,感受代码复用性的提升,比如原来分散在datamethods里的表单验证逻辑,用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就有响应式。

  • 如果是第三方库返回的非响应式对象(比如后端接口返回的用户信息),得用reactiveref包一下,不然修改不触发更新。

    // 错误:直接赋值非响应式对象
    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.$onthis.$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的modulesmutations这些“繁文缛节”,用更简洁的函数式写法:

  • 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的mapStatemapActions清爽太多。

构建工具: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语法糖,把原来的datamethods改成reffunction,测试响应式和事件逻辑是否正常,跑通一个组件后,再逐步替换列表、表单这些复杂组件。

举个🌰:重构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,原来的mountshallowMount用法有变化,比如现在要导入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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门