本次升级基于gogocode快捷转换gogocode升级体验,汇总的部分问题可能与gogocode有关,但仍可作为vue2升级vue3过程中报错问题的解决参考
Vue变更
1.实例创建与全局实例变量
window.$vueApp = Vue.createApp(App)
window.$vueApp.use(router) // router在挂载之前,否则无法渲染
window.$vueApp.mount('#app')
window.$vueApp.use(store)
window.$vueApp.config.globalProperties.$http = axios
2.路由定义
// Catch all routes ("*") must now be defined using a param with a custom regexp.
path: '*' => path: '/:catchAll(.*)'
component: (resolve) => {
require.ensure([], () => {
return resolve(require('@views/login/Index.vue'))
}, 'login')
}
=>
component: () => import('@views/login/Index.vue'),
3.渲染函数
// 需手动全局引入h渲染函数
import { h } from 'vue'
createElement => h
// 渲染函数传参全部扁平化,将外层包裹去掉
h(CtrlViewMap[type] || CtrlViewMap.native, {
protocol: p,
uitype,
fixedStyle: style,
clickCtrlView: this.clickCtrlView,
eventScroll: (e, layoutScrollTop) => {
this.$emit('event-scroll', e, layoutScrollTop)
},
key: p.code,
class: isHidden
})
4.插槽
// Vue3中插槽获取的是插槽渲染函数,不再是Vue2中直接的虚拟节点VNode
this.$slots.default => this.$slots.default()
this.$slots['default'] => this.$slots['default']()
// scopedSlots统一替换为slots
scopedSlots => slots
// 插槽变量采用对象解构
"text, record" => "{text, record}"
5.数组监听
- 问题:gogocode在对数组监听函数进行语法转换时增加deep选项深度监听,但项目中不需要深度监听,增加deep选项反而导致报错(涉及Vue3中数组监听没有deep情况下只有数组替换才触发监听,数组变化不触发)
- 报错:
TypeError: Cannot set properties of null (setting '__vnode') at patchElement (runtime-core.esm-bundler.js:4626:1)
- 解决:去除deep字段
6.事件总线
- gogocode快捷转换现状
- 生成gogocodeTransfer文件并局部引入,会生成多个相同文件且转化失败的文件需要手动引入,可尝试全局引入该方法
- 部分API转换失败需要手动转化,转换成功的大部分是不需要转化的父组件this.$emit事件
- 基于gogocode快捷转换后进行手动调整
/*
正则替换:`'[^']*gogocodeTransfer[^']*'`替换为`'@/utils/gogocodeTransfer'`
批量删除gogocodeTransfer引入
批量替换转换后的$on(Bus)/$off(Bus)/$emit(Bus)/$once(Bus)恢复为Bus.$on/Bus.$off/Bus.$emit/Bus.$once,部分链式调用需要注意单个恢复
*/
// main/index
import { $on, $off, $emit, $once} from '@/utils/gogocodeTransfer'
// 兼容this未转换的指令
window.$vueApp.config.globalProperties.$on = $on
window.$vueApp.config.globalProperties.$off = $off
window.$vueApp.config.globalProperties.$once = $once
window.$vueApp.config.globalProperties.$emit = $emit
// 兼容转换后的$emit(this),本身并不需要转换,vue3仍支持this.$emit触发子组件事件
window.$emit = $emit
window.$on = $on
window.$off = $off
window.$once = $once
// bus/index(对原事件总线改造而不使用gogocode转换)
import * as Vue from 'vue'
const busInstance = {}
const eventPool = {}
const eventRegistryMap = new WeakMap()
function getRegistry() {
let events = eventRegistryMap.get(busInstance)
if (!events) {
eventRegistryMap.set(busInstance, (events = Object.create(null)))
}
return events
}
function addEvent(eventName, fn) {
if (!fn) {
return
}
if (!eventPool[eventName]) {
eventPool[eventName] = []
}
eventPool[eventName].push(fn)
}
function removeEvent(eventName, fn) {
if (!eventPool[eventName]) return
if (!fn) {
delete eventPool[eventName]
return
}
const events = eventPool[eventName].filter((event) => event !== fn)
eventPool[eventName] = events
}
busInstance.$on = function (eventName, fn) {
addEvent(eventName, fn)
if (Array.isArray(eventName)) {
eventName.forEach((e) => busInstance.$on(e, fn))
} else {
const events = getRegistry()
if(events && events[eventName]) events[eventName].push(fn)
else if(events){
events[eventName] = []
events[eventName].push(fn)
}
}
return busInstance
}
busInstance.$off = function (eventName, fn) {
removeEvent(eventName, fn)
const vm = busInstance
// all
if (!eventName) {
eventRegistryMap.set(busInstance, Object.create(null))
return vm
}
// array of events
if (Array.isArray(eventName)) {
eventName.forEach((e) => busInstance.$off(e, fn))
return vm
}
// specific event
const events = getRegistry()
const cbs = events[eventName]
if (!cbs) {
return vm
}
if (!fn) {
events[eventName] = undefined
return vm
}
events[eventName] = cbs.filter((cb) => !(cb === fn || cb.fn === fn))
return vm
}
busInstance.$once = function (eventName, fn) {
const wrapped = (...args) => {
busInstance.$off(eventName, wrapped)
fn.call(busInstance, ...args)
}
wrapped.fn = fn
busInstance.$on(eventName, wrapped)
return busInstance
}
busInstance.$emit = function (eventName, ...args) {
const cbs = getRegistry()[eventName]
if (cbs) {
cbs.map((cb) => cb.apply(busInstance, args))
}
return busInstance
}
busInstance.$getOns = (eventName) => {
if (!eventName) {
return eventPool
}
return eventPool[eventName] || []
}
export default busInstance
7.各类变化相关
- Vue3中对象新增删除属性会触发响应式更新,若项目中存在对象新增删除属性,需注意Vue3触发响应式更新是否会影响当前逻辑
- 若实例中未显式定义components,this.$options中无法获取到components
- 异步组件不再是函数形式引入,无法作为函数执行
- $mount在Vue3不支持
- iframe赋值为非响应式变量造成的跨域报错
Failed to read a named property '__v_isRef' from 'Window': Blocked a frame with origin "http://127.0.0.1:9088" from accessing a cross-origin frame.
(暂不清楚原因,通过赋值为响应式变量解决)