Vue2升级Vue3中容易遇到的问题

2024/01/13

最近在将一个运行了3年的Vue2项目升级到Vue3,过程中踩了不少坑,所以记录一下在实际开发中的变化。

一、生命周期钩子

1. 被移除的钩子

  • beforeCreate 和 created 被 setup() 替代
  • beforeDestroy → 改用 beforeUnmount
  • destroyed → 改用 unmounted

2. 组合式API中的异步陷阱

// 错误示例:在setup中直接调用异步钩子  
setup() {  
  onMounted(async () => {  
    await fetchData(); // 可能导致内存泄漏  
  });  
}  

// 正确方案:分离异步逻辑  
setup() {  
  const loadData = async () => { /* ... */ };  

  onMounted(() => {  
    loadData();  
  });  
}  

二、模板语法

1. v-model的破坏性变更

<!-- Vue 2:value + @input -->  
<Child v-model="pageTitle" />  

<!-- Vue 3:modelValue + update:modelValue -->  
<Child v-model:title="pageTitle" />  

<!-- 多v-model支持,可以自定义名称 -->  
<Form v-model:name="name" v-model:age="age" />  

2. 事件监听符的变更

<!-- Vue 2监听原生事件 -->  
<comp @click.native="handleClick" />  

<!-- Vue 3移除.native -->  
<comp @click="handleClick" />  

<!-- 需要传递原生事件时 -->  
<comp v-on="listeners" />  

3.作用域插槽语法变更

  • 使用 v-slot 的统一语法
  • 简写方式变化:#default 替代 slot=”default”
  • 具名插槽语法如下:
<template v-slot:header>
  <!-- 内容 -->
</template>

<!-- 简写 -->
<template #header>
  <!-- 内容 -->
</template>

4.vue3中 v-if 优先级大于 v-for

三、全局API

1. Vue.prototype的替代方案

// Vue 2全局挂载  
Vue.prototype.$http = axios;  

// Vue 3使用provide/inject  
// main.js  
app.provide('http', axios);  
// 组件中获取  
const http = inject('http');  

// Vue 3使用globalProperties
// main.js
const copy = (e) => {
  return JSON.parse(JSON.stringify(e));
};
app.config.globalProperties.$copy = copy;
// 组件中使用
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
const list = [1,2,3]
const list2 = proxy.$copy(list);

2. 过滤器(Filter)的废弃处理

// 旧版过滤器  
{{ price | currency }}  

// 替代方案:全局方法/计算属性  
// 注册全局方法  
app.config.globalProperties.$currency = (val) => `${val}`;  

// 模板中使用  
{{ $currency(price) }}  

3.事件总线EventBus不再推荐使用

四、第三方库的兼容性问题

1. elementUI的版本适配

# Element UI → Element Plus  
npm uninstall element-ui  
npm install element-plus @element-plus/icons-vue  

# 按需引入配置变更  
// vite.config.js  
import AutoImport from 'unplugin-auto-import/vite';  
import Components from 'unplugin-vue-components/vite';  
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';  

plugins: [  
  AutoImport({ resolvers: [ElementPlusResolver()] }),  
  Components({ resolvers: [ElementPlusResolver()] })  
]  

2. Vuex到Pinia的迁移

// Vuex模块化  
const moduleA = {  
  state: () => ({ count: 0 }),  
  mutations: { increment(state) { state.count++ } }  
}  

// Pinia等效实现  
export const useStore = defineStore('moduleA', {  
  state: () => ({ count: 0 }),  
  actions: {  
    increment() { this.count++ }  
  }  
});  

五、项目配置

1. Vue CLI到Vite的迁移

// 旧版vue.config.js → vite.config.ts  
// 路径别名配置变更  
resolve: {  
  alias: {  
    '@': path.resolve(__dirname, './src')  
  }  
}  

// 环境变量前缀变更  
// 原process.env → import.meta.env  
VITE_API_URL=xxx # .env文件需以VITE_开头  

2. IE11兼容性处理

# 安装polyfill  
npm install @vitejs/plugin-legacy --save-dev  

# vite.config.js  
import legacy from '@vitejs/plugin-legacy';  

plugins: [  
  legacy({  
    targets: ['defaults', 'not IE 11'] // 明确放弃IE11  
  })  
]