手动迁移之前,请先使用 vue-codemod 工具进行自动转换。工具使用方法请参考 使用指导 。
本迁移手册是基于已经转换的项目中的实际遇到的问题进行总结的,在更多真实项目的转换过程中,可能会遇到其他问题,欢迎提交 issue 或者 PR 进行贡献。
Vue 的版本需要不低于 2.6.0,如果使用了 2.6.0 之前的版本,可能部分语法无法自动转换。
dependency 中有不支持 Vue 3 的库,暂时无法实现升级,需要选择其他依赖库替代,并重写部分逻辑。
目前已经支持 Vue 3 的 UI 框架库有:
具体是否支持 Vue 3 ,可参考 Vue2ToVue3
-
使用插件的方式对
全局 API
进行转换-
将非 main.js 中的全局 API 装载为插件形式
Vue 2 中:
// directive/index.js import Vue from 'vue' import myDirective from '@/directive/myDirective' Vue.directive('myDirective', myDirective)
Vue 3 中:
// directive/index.js import myDirective from '@/directive/myDirective' export default { install: app => { app.directive('myDirective', myDirective) } }
-
并在 main.js 中引用使用该插件
// main.js import MyDirective from '@/directive' Vue.createApp(App).use(myDirective)
-
-
使用
window.app=app
的方式对全局配置
进行转换-
main.js 中配置
全局 app
实例// main.js const app = Vue.createApp(App) window.app = app // 配置全局 app 实例 app.mount('#app')
-
非 main.js 中的配置
Vue 2 中:
// message/index.js Vue.prototype.$baseMessage = () => { Message({ offset: 60, showClose: true }) }
Vue 3 中:
// message/index.js app.config.globalProperties.$baseMessage = () => { Message({ offset: 60, showClose: true }) }
注意:使用
window.app
需要结合 Vue 框架中 js 代码的执行顺序,只有在 main.js 中window.app = app
配置语句之后运行的代码才可以使用window.app
。已知在 main.js 之后运行的代码部分为:(1) 在 Vue 组件的export default {}
中运行;(2) 在 main.js 使用app.use()
的 js 文件。
-
详情见:官方迁移指导
slot
属性的语法在 Vue 2.6.0 版本开始被废弃了,需要使用 v-slot
指令来支持具名插槽。转换工具的 slot-attribute
规则会将 slot
属性转换为 v-slot
指令的用法:
<base-layout>
<p slot="content">2.5 slot attribute in slot</p>
</base-layout>
会被转换为:
<base-layout>
<template v-slot:content>
<p>2.5 slot attribute in slot</p>
</template>
</base-layout>
对于同时使用了 v-if
与 v-else
指定的具名插槽来说,工具的转换则会产生错误:
<el-button v-if="showCronBox" slot="append" @click="showBox = false"></el-button>
<el-button v-else="showCronBox" slot="append" @click="showBox = true"></el-button>
将会被转换为:
<template v-slot:append>
<el-button v-if="showCronBox" @click="showBox = false"></el-button>
</template>
<template v-slot:append>
<el-button v-else="showCronBox" slot="append" @click="showBox = true"></el-button>
</template>
由于 v-if
与 v-else
被分隔到两个 <template>
中,将会编译报错找不到 v-if
,需要将 v-if
与 v-else
放到同一个 <template>
标签范围内:
<template v-slot:append>
<el-button v-if="showCronBox" @click="showBox = false"></el-button>
<el-button v-else="showCronBox" slot="append" @click="showBox = true"></el-button>
</template>
详情见官方迁移指导
详情见官方迁移指导
在 Vue 3 中,$on
,$off
,$once
实例方法已经被移除,应用实例不再实现事件触发接口,因此无法使用这些 API 从组件内部监听组件自己发出的事件。但是该 eventHub
模式可以被替换为实现了事件触发器接口的外部库。
详情见官方迁移指导
-
添加
mitt
依赖yarn add mitt // or npm install mitt
-
创建
mitt
实例import mitt from 'mitt' const bus = {} const emitter = mitt() bus.$on = emitter.on bus.$off = emitter.off bus.$once = emitter.once export default bus
-
main.js 中添加全局事件总线声明
// main.js import bus from '@/bus' const app = createApp(App).mount('#app') app.config.globalProperties.$bus = bus
>>>
和/deep/
已经不支持了/deep/ .el-input {}
更改为:deep(.el-input) {}
v-deep:: .bar {}
更改为::v-deep(.bar) {}
Vue 2 中事件内部语句可以通过换行符
作为分隔符:
<button @click="
item.value = ''
clearTag()
">
</button>
但是在 Vue 3 中,需要添加 ;
或者 ,
作为分隔符:
<button @click="
item.value = '';
clearTag()
">
</button>
详情见:官方迁移指导
Router 3 中,Vue Router 是一个类,可以通过 prototype
访问 push
方法,但是在 Router 4 中,Router 是一个实例,需要通过实例去访问 push
方法。
在 Router 3(配套 Vue 2)中:
import VueRouter from 'vue-router'
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function (location, onResolve, onReject) {
if (onResolve || onReject) {
return originalPush.call(this, location, onResolve, onReject)
}
return original.call(this, location).catch(e => {
if (e.name !== 'NavigationDuplicated') {
return Promise.reject(e)
}
})
}
在 Router 4(配套 Vue 3)中
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// 注意:重写 push 方法要在创建 router 实例之后
const originalPush = router.push
router.push = function (location, onResolve, onReject) {
if (onResolve || onReject) {
return originalPush.call(this, location, onResolve, onReject)
}
return original.call(this, location).catch(e => {
if (e.name !== 'NavigationDuplicated') {
return Promise.reject(e)
}
})
}
详情见:官方迁移指导
现在必须使用指定的 regex
参数来定义所有路由(*
、/*
)。
在 Router 3(配套 Vue 2)中,可以直接定义 *
路由:
// router/index.js
const asyncRoutes = [
{
path: '*',
redirect: '/'
}
]
在 Router 4(配套 Vue 3)中,则需要使用 pathMatch
来定义 path:
// router/index.js
const asyncRoutes = [
{
path: '/:pathMatch(.*)*',
redirect: '/'
}
]
可能会导致部分组件渲染失败,例如下面的 RuleFilter.vue
组件:
watch: {
$route: {
immediate: true,
handler (to, from) {
if (to.name === 'RuleFilterTbl') {
const param = !!this.$refs.internal ? this.$refs.internal.selectItem : {}
this.$bus.$emit('filterSearch', param)
}
}
}
}
其中 this.$bus.$emit
将会激发事件失败,因为事件还不存在,事件在组件挂载之后才会注册到 $bus
上:
// RuleFilterTbl.vue
mounted() {
this.$bus.$on('filterSearch', this.search)
this.$bus.$on('filterReset', this.reset)
}
所以触发 filterSearch
事件时需要等待组件挂载,修改为:
watch: {
$route: {
immediate: true,
handler (to, from) {
if (to.name === 'RuleFilterTbl') {
const param = !!this.$refs.internal ? this.$refs.internal.selectItem : {}
// 判断路由是否完成初始化
this.$router.isReady().then(() => {
this.$bus.$emit('filterSearch', param)
})
}
}
}
}
目前 Element UI 提供了适配 Vue 3 的组件库 Element Plus,vue-codemod
完成了依赖升级与依赖替换等大部分的升级场景,但是 Element-Plus
仍然处于 beta 测试中,部分功能可能不稳定,需要开发者手动升级。
部分全局样式的引入需要手动替换路径:import('element-ui/lib/theme-chalk/index.css')
替换为 import('element-plus/lib/theme-chalk/index.css')
必须使用 <template>
配合 slot 的形式,例如:
<el-table>
<span slot-scope="scope">{{ scope.row.num }}</span>
</el-table>
需要切换成:
<el-table>
<template #default="scope">
<span>{{ scope.row.num }}</span>
</template>
</el-table>