项目vue2 升级成 vue3

背景:你是前端,日常业务 Vue2,考虑升级,分为两种路径:完整迁移(整个项目改成 Vue3 + Vite)渐进式兼容(vue2‑bridge,混合模式)。Vue2 已经停止维护,长远建议迁移;但业务项目优先评估业务风险。

一、核心变化总览

  1. 构建工具:webpackVite(推荐),也可以继续 webpack5
  2. 框架:vue 2.xvue 3.x
  3. 语法:选项式API可以继续用;推荐组合式API <script setup>
  4. 库:
  • vue-router@3vue‑router@4
  • vuex@3pinia(优先,vuex还能用但不再迭代)
  • element‑uielement‑plus

重点:ElementUI 不兼容 Vue3,必须替换 Element‑Plus。

二、方案A:渐进迁移(风险最低,适合老业务大项目)

使用 @vue/compat 兼容构建模式,Vue3可以跑大部分Vue2代码,逐步改造,不用一次性全部改完。
适合:业务复杂、迭代不能停、不能一次性全量重构。

  1. 安装依赖
npm install vue@3 @vue/compat
npm install -D @vitejs/plugin-vue vite
  1. vite配置开启兼容模式
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          compatConfig: { MODE: '2' }
        }
      }
    })
  ]
})
  1. main.js改动
// Vue2 旧代码
// import Vue from 'vue'
// new Vue({el:'#app',render:h=>h(App)})

// Vue3 compat模式
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

⚠️ compat只是过渡,会有警告,逐个修复警告,逐步把组件改成标准Vue3写法,最后移除compat。

三、方案B:完整迁移(新项目/小项目,彻底Vue3)

1. 破坏性改动高频清单(你改项目最常踩坑)

  1. 实例this
    Vue3 setup 里面没有 this,改用 ref / reactive
<script setup>
import {ref} from 'vue'
const count = ref(0)
</script>
  1. 生命周期
    beforeDestroybeforeUnmount
    destroyedunmounted
  2. 事件:$listeners 被移除,v‑on直接透传
  3. v‑if 和 v‑for 优先级调换:Vue2 v‑for优先;Vue3 v‑if优先,不要写在同一标签。
  4. 过滤器 filters 整个移除,不能用,改成方法或者计算属性。
// ❌ filters: { format(){}}
// ✅ computed / function
  1. 组件 v‑model 改动
<!-- vue2 -->
<Child v-model="val"/>

<!-- vue3 默认 -->
<Child v-model="val"/>
<!-- 本质是 :modelValue + @update:modelValue -->
  1. 双向绑定 .sync 语法删除,全部替换成 v‑model:xxx
  2. 全局API变更
// vue2
// Vue.prototype.$xxx = xxx

// vue3
app.config.globalProperties.$xxx = xxx
  1. 虚拟DOM:根节点不再限制单根,template可以多个根元素。

2. 周边库替换对照表

Vue2Vue3
vue‑router@3vue‑router@4
vuex@3pinia(推荐)
element‑uielement‑plus
axios不变

3. main.js完整示例

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)
app.use(router)
app.use(createPinia())
app.use(ElementPlus)
app.mount('#app')

四、迁移实操步骤(工程落地顺序)

  1. 备份代码,拉独立迁移分支,不要直接改主分支
  2. 升级/替换核心依赖 vue、router、UI库
  3. 修复构建报错:filters、.sync、生命周期名称、v‑for+v‑if
  4. 解决UI组件API差异(element‑ui → element‑plus很多属性名字变)
  5. 修复全局 Vue.prototypeglobalProperties
  6. 跑单元测试,手工全量回归业务页面
  7. 逐步把组件从选项式API改 <script setup>(可选,不改也可以跑)

五、现实业务建议

  1. 如果是外包老项目,业务很重:优先compat渐进模式,不要一次性大改,大改容易出线上bug,甲方也不希望无业务改动大规模重构。
  2. 小页面、新页面直接写 <script setup>,旧页面保留选项式慢慢迭代。
  3. 不要一上来就全部重写业务逻辑,优先保证功能跑通,再优化写法。

六、常见坑

  1. element‑ui组件很多props改名,比如 size="small" 部分、弹窗事件、表单校验规则;
  2. 第三方旧组件库,很多不支持Vue3,要找vue3版本替代;
  3. webpack迁移Vite:部分webpack特有插件需要替换,别名配置写法略有差异。

另附

Vue2 + Element‑UI → Vue3 + Vite + Element‑Plus 完整迁移实操

适用:把原有 webpack‑vue2‑element‑ui 项目,完整迁移到 Vite+Vue3+Element‑Plus,不是兼容模式,彻底升级。
⚠️重要:不要直接在main分支改,新建迁移分支,原有代码做好备份

1、新建vite基础项目(参考)

# 创建vite‑vue3模板
npm create vite@latest my‑vue3‑proj -- --template vue
cd my‑vue3‑proj
npm install
# 安装周边
npm install vue-router@4 pinia element-plus axios
npm install -D unplugin-vue-components unplugin-auto-import

unplugin‑xxx 自动导入element‑plus组件,不用每个页面手动import,生产项目必用。

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(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
      imports: ['vue','vue-router']
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ],
  resolve:{
    alias:{
      '@': resolve(__dirname,'./src')
    }
  },
  server:{
    port:8080
  }
})

2、main.js 新旧对比

Vue2 webpack main.js(旧)

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.use(ElementUI)
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Vue3 Vite main.js(新)

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
// 如果不用自动导入插件,才需要全局引入element‑plus
// import ElementPlus from 'element-plus'
// import 'element-plus/dist/index.css'

const app = createApp(App)
app.use(router)
app.use(createPinia())
// app.use(ElementPlus)
app.mount('#app')

建议优先使用上面 unplugin 自动导入,减少打包体积。

3、路由 router/index.js

vue‑router@4 写法变化

import { createRouter, createWebHistory } from 'vue-router'
const routes = [
  { path:'/', component:()=>import('@/views/home.vue') }
]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router

❌不再使用 new VueRouter()

4、状态管理:vuex → pinia

vuex不再推荐,迁移pinia。
store示例 src/stores/user.js

import { defineStore } from 'pinia'
export const useUserStore = defineStore('user',{
  state:()=>({
    name:'',
    token:''
  }),
  actions:{
    setToken(val){
      this.token = val
    }
  }
})

5、业务组件迁移重点(高频踩坑)

5‑1 生命周期改名

beforeDestroy → beforeUnmount
destroyed → unmounted

5‑2 移除 filters

vue3没有过滤器,改用 computed 或者页面内方法。

5‑3 .sync 全部删除,替换 v‑model:属性名

<!-- vue2 -->
<el-dialog :visible.sync="showDialog"/>

<!-- vue3 -->
<el-dialog v-model:visible="showDialog"/>

5‑4 v‑if 和 v‑for

vue3中 v‑if优先级高于v‑for,禁止写同一个标签;放到外层容器分开。

5‑5 this 问题

<script setup> 里面没有this;
选项式API还可以继续用this。

<script setup>
import { ref,reactive } from 'vue'
const showDialog = ref(false)
</script>

5‑6 全局挂载

// vue2
// Vue.prototype.$api = api

// vue3
app.config.globalProperties.$api = api

5‑7 ElementUI → ElementPlus 重要改动

  1. 弹窗 this.$alert / this.$confirm
// vue2
this.$confirm('确定?')

// vue3 script‑setup中
import { ElMessageBox } from 'element-plus'
ElMessageBox.confirm('确定?')
  1. 表单校验:this.$refs.form.validate() 用法大体不变,部分prop改名
  • size 枚举值变化
  • el‑table 部分事件、属性变更;sort‑change、selection‑change 事件名不变,但参数细节有差异
  • el‑input value‑syncv‑model
  1. 图标:element‑plus图标单独包 @element‑plus/icons‑vue,需要单独安装导入
npm install @element-plus/icons-vue

6、迁移落地实操步骤(真实项目)

  1. 创建vite‑vue3新项目,搭建基础架子:router、pinia、element‑plus自动导入
  2. 把旧项目src下面:api、utils、assets、views组件拷贝过来
  3. 逐个修复报错:
  • .sync语法
  • filters过滤器
  • beforeDestroy生命周期
  • element‑ui组件属性、弹窗调用方式
  1. 替换图标依赖
  2. 路由检查,404、history模式
  3. 本地完整跑一遍所有业务页面,回归表单、弹窗、表格、分页
  4. 处理环境变量:
  • webpack process.env → vite使用 import.meta.env
  1. 打包测试 npm run build

⚠️老项目如果大量第三方组件,要看是否支持vue3;不支持要找替代。

7、两种组件写法选择

  1. 选项式API(和vue2几乎一样),可以继续写,不用全部改成setup,适合快速迁移:
<script>
export default {
  data(){return {}},
  methods:{}
}
</script>
  1. 是推荐新写法,新页面优先用,老页面可以不改。
  1. 8、风险提醒
  2. 如果是线上业务不能停,不建议一次性整体大迁移;可以考虑新建项目,业务页面分批迁移。
  3. elementUI 到 ElementPlus 有很多细节 UI 差异,表格、弹窗、校验,需要人工回归,会有不少隐性 bug。
  4. 如果你需要,我可以给你一份:一个完整 vue2 选项式组件,转成 <script setup> 的对照示例。

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注