首页技术专题博客目录关于与联系

Vue 3 迁移指南:我们踩过的那些坑

从 Vue 2 迁移到 Vue 3,耗时3个月,踩了无数坑。事件总线、Vuex、插槽语法全变了,完整记录迁移经验。

去年Q3,我们开始把项目从 Vue 2 迁移到 Vue 3。原以为一个月就能搞定,结果花了三个月。

这篇文章记录我们踩过的坑,希望能帮到正在迁移的你。

为什么要迁移

Vue 2 官方已经停止维护了,不迁移不行:

  • 安全漏洞不会修了
  • 新的库都不支持 Vue 2
  • 招人也不好招(谁还想写 Vue 2)

而且 Vue 3 确实有优势:

  • 性能更好
  • TypeScript 支持更好
  • Composition API 更灵活

迁移策略

我们没有一次性全部迁移,而是分阶段:

阶段1:评估影响范围

先跑一遍 @vue/compat(Vue 2 兼容模式),看看哪些代码会报错。

npm install @vue/compat 

然后配置 webpack:

module.exports = { resolve: { alias: { vue: '@vue/compat' } } }

跑一遍项目,控制台会显示所有不兼容的 API。

我们项目有 200+ 个警告。

阶段2:先修简单的

有些改动很简单,比如:

  • $on$off$once 被移除了 → 改用 mitt
  • $children 被移除了 → 改用 ref
  • filter 被移除了 → 改用 computed 或方法

这些改动风险低,优先处理。

阶段3:重点改造核心组件

有些组件用了很多 Vue 2 的特性,需要重写:

  • 大量用了 this
  • 用了 mixins
  • 用了全局过滤器

这些组件改成 Composition API,逻辑更清晰。

阶段4:升级第三方库

很多库也要升级:

  • Vue Router 3 → 4
  • Vuex 3 → 4(后来换成了 Pinia)
  • Element UI → Element Plus

这是最麻烦的部分。

踩过的坑

坑1:v-model 的变化

Vue 2 里,v-model 绑定的是 value prop 和 input 事件。

Vue 3 里,v-model 绑定的是 modelValue prop 和 update:modelValue 事件。

我们有个组件:

// Vue 2 props: ['value'], methods: { handleChange(val) { this.$emit('input', val); } }

要改成:

// Vue 3 props: ['modelValue'], methods: { handleChange(val) { this.$emit('update:modelValue', val); } }

如果不改,v-model 就失效了。

坑2:事件总线不能用了

Vue 2 里,我们用 $on$emit 做事件总线:

// EventBus.js export default new Vue(); // A组件 EventBus.$emit('some-event', data); // B组件 EventBus.$on('some-event', callback);

Vue 3 移除了 $on$off,事件总线不能用了。

我们改用 mitt

import mitt from 'mitt'; export const emitter = mitt(); // A组件 emitter.emit('some-event', data); // B组件 emitter.on('some-event', callback);

全局搜索 $on$emit,改了100多处。

坑3:Vuex 要重写

Vuex 4 的 API 变化不大,但 TypeScript 支持还是很差。

我们直接换成了 Pinia:

// Vuex store.dispatch('user/login', payload); // Pinia userStore.login(payload);

Pinia 的好处:

  • 不用写 mutations
  • TypeScript 支持完美
  • 模块自动注册

但迁移成本也不小,我们花了一周时间。

坑4:插槽语法变了

Vue 2 里:

<template slot="header" slot-scope="{ data }"> {{ data.title }} </template>

Vue 3 里:

<template #header="{ data }"> {{ data.title }} </template>

还好有兼容模式,但迁移后都改成了新语法。

坑5:全局 API 改了

Vue 2 里,很多东西挂在 Vue 上:

import Vue from 'vue'; Vue.prototype.$http = axios; Vue.use(VueRouter); Vue.component('MyComponent', MyComponent);

Vue 3 里,要用 app 实例:

import { createApp } from 'vue'; const app = createApp(App); app.config.globalProperties.$http = axios; app.use(router); app.component('MyComponent', MyComponent);

所有插件初始化的代码都要改。

坑6:响应式 API 的坑

Vue 3 的 reactive 不能直接赋值:

let state = reactive({ count: 0 }); // ❌ 这样不行,会丢失响应式 state = { count: 1 }; // ✅ 要这样 state.count = 1;

我们有个地方直接赋值了,结果数据不更新,debug了半天。

坑7:生命周期钩子变了

Options API 里,beforeDestroydestroyed 改名了:

// Vue 2 beforeDestroy() {}, destroyed() {} // Vue 3 beforeUnmount() {}, unmounted() {}

Composition API 里,要加 on 前缀:

import { onMounted, onUnmounted } from 'vue'; onMounted(() => {}); onUnmounted(() => {});

Element Plus 的坑

从 Element UI 到 Element Plus,也踩了不少坑:

1. 图标要单独导入

// Element UI <el-button icon="el-icon-search"></el-button> // Element Plus import { Search } from '@element-plus/icons-vue'; <el-button :icon="Search"></el-button>

所有图标都要手动导入,很麻烦。

2. 表单校验 API 变了

// Element UI this.$refs.form.validate(valid => {}); // Element Plus const formRef = ref(); formRef.value.validate(valid => {});

3. 样式变量名变了

Element UI 的 CSS 变量是 --el-color-primary,Element Plus 是 --el-color-primary

等等,好像没变?但有些确实变了,比如 --color-primary--el-color-primary

迁移工具

有些工具能帮上忙:

1. @vue/compat

兼容模式,让 Vue 2 的代码在 Vue 3 里跑起来,同时给出警告。

2. gogocode-plugin-vue

自动转换代码:

npx gogocode -s ./src -t gogocode-plugin-vue -o ./src-vue3

但转换率只有60%左右,还是要手动改。

3. eslint-plugin-vue

配置 Vue 3 的规则,自动检查不兼容的写法。

迁移后的效果

指标Vue 2Vue 3
首屏加载2.5s1.8s
打包体积1.2MB900KB
渲染性能-提升30%

值得迁移。

给正在迁移的人的建议

  1. 用兼容模式:先跑起来,再慢慢改
  2. 分模块迁移:别想一次性全改完
  3. 写好测试:迁移过程中容易出bug
  4. 先改核心组件:高频使用的优先改
  5. 考虑换 Pinia:比 Vuex 好用太多
  6. 留出足够时间:比你预期的要久

迁移很累,但值得。


参考资料:Vue 3 迁移指南

评论区