互动接收端 UI 组件如何兼容 Vue2 和 Vue3
互动接收端 SDK
保利威云直播产品能够在直播过程发动签到、抽奖、问卷等互动。对应地,在观看端也须要出现这些互动。为了让保利威云直播观看页以及客户定制观看页都能不便地接入这些性能,咱们把互动性能做成了一个 SDK,即互动接收端 SDK。
互动接收端 SDK 严格依照逻辑 与 UI 拆散的形式开发。基于逻辑层能够开发界面不同的 UI,甚至是不同技术栈的 UI。而默认的 UI 则是基于 Vue.js 2.6 开发的。
家喻户晓,Vue.js 3.x 曾经正式公布。如果把基于 Vue.js 2.6 开发的这套 UI 组件用于 Vue.js 3.x 的我的项目,会呈现报错;如果从新开发一套能适配 Vue.js 3.x 的 UI 组件,则须要保护两套代码,不利于后续性能迭代更新。
本文次要讲述如何在一份代码的根底上,构建出两个版本的 Vue.js 组件,从而适配 Vue.js 2.6 与 Vue.js 3.x(下文别离把 Vue.js 2.6 和 Vue.js 3.x 简称为 Vue2 和 Vue3)。
Vue2 和 Vue3 兼容架构介绍
在原有的代码根底上,别离编译构建出 Vue2 和 Vue3 的包。
- 长处:后续性能有新增或者优化,互动性能的源代码只须要改变一次。
- 毛病:无奈援用不兼容 Vue2 和 Vue3 的 UI 组件库。不能应用 Vue2、Vue3 不兼容的语法,包含 Vue3 新个性。
为 UI 组件我的项目搭建 Vue3 构建环境
原来 UI 组件我的项目目录构造如下:
-
目录 ui
-
目录 vue2
- 目录 build
- 目录 src
- 文件 package.json
- ..
-
为了搭建 Vue3 的构建环境,在 ui 目录下中新建一个 vue3 目录,并将 vue2 目录下除 src 以外的所有配置文件拷贝过去。目录构造如下:
-
目录 ui
-
目录 vue2
- 目录 build
- 目录 src
- 文件 package.json
- …
-
目录 vue3
- 目录 build
- 文件 package.json
- …
-
接下来批改 vue3 目录下的配置文件, 在 package.json 将 vue
更新到 3.1, 装置雷同版本的 @vue/compat
和 @vue/compiler-sfc
,并将 vue-loader 降级到 16 以上
"dependencies": {
- "vue": "^2.6.12",
+ "vue": "^3.1.0",
+ "@vue/compat": "^3.1.0",
- "vue-loader": "15.9.7"
+ "vue-loader": "16.0.0"
...
},
"devDependencies": {+ "@vue/compiler-sfc": "^3.1.0"}
在 Webpack 的构建配置中为 vue
设置别名 @vue/compat
后,能够检测在 Vue3 中不兼容的代码片段。
设置 webpack 入口文件门路指向 src 目录下的源代码。
const srcDirname = path.resolve(__dirname, '../../vue2/src');
module.exports = {entry: path.join(srcPath, 'main.js'),
resolve: {
alias: {
vue: '@vue/compat',
'@': srcDirname
}
},
...
}
至此环境搭建结束。
解决代码兼容问题
运行我的项目后,关上 demo 页面仍有问题,并且控制台出现异常信息,须要修复这些问题,我的项目能力运行失常。
问题次要是因为以下起因导致的:(更多兼容问题参考官网文档)
- Vue2 和 Vue3 别离通过 extend 和 createApp 创立节点
- 在 Vue3 中
$options
属性不可批改 - Vue3 不反对过滤器 filter
- Vue3 不反对 2.6 以下的插槽语法
- Vue3 不反对全局办法
$set
- 生命周期 destroy 和 beforeDestroy,在 Vue3 更名为 unmounted 和 beforeUnmount
Vue2 和 Vue3 别离通过 extend 和 createApp 创立节点
对于无奈同时在 Vue2 和 Vue3 运行的代码,可通过判断版本号别离写两套代码
import Vue from 'vue';
function isVue3() {return Vue.version.startsWith('3.');
}
if(isVue3()) {
// Vue3 环境代码
const TipApp = Vue.createApp(SubmitTip, propsData);
TipApp.mount(wrapContain);
// 业务代码...
} else {
// Vue2 环境代码
const Componet = Vue.extend(SubmitTip, propsData);
const TipApp = new Componet({propsData});
TipApp.$mount(wrapContain);
// 业务代码...
}
在 Vue3 中$options
属性不可批改
起因:在 Vue2 中 $options
能够动静批改,但在 Vue3 中 $options
属性只可读,不可批改。
解决方案:将本来放在 $options
的逻辑代码,迁徙到data()
下。
// 原来的写法
export default {
$i18n: null,
provide() {
// 初始化多语言实例
this.$options.$i18n = new I18n(this.$options.langs);
// 设置响应式语言属性
this.$options.$i18n.updateLocale(() => this.lang);
return {getI18n: () => this.$options.$i18n
};
},
...
}
// 代替写法
export default {data() {
return {_i18n: null}
}
provide() {
// 初始化多语言实例
this._i18n = new I18n(this.langs);
// 设置响应式语言属性
this._i18n.updateLocale(() => this.lang);
return {getI18n: () => this._i18n,
};
},
...
}
Vue3 不反对过滤器 filter
能够应用 methods 代替 filter。例如:
<!-- 过滤器 filter 写法 -->
<div> {{ date | dateFilter }}</div>
<!-- methods 写法 -->
<div> {{ dateFilter(date) }}</div>
Vue3 不反对 2.6 以下的插槽语法
应用最新插槽语法能够在 Vue2 和 Vue3 同时运行。例如:
<!-- 废除语法 -->
<template>
<template slot="header">
<h1> title </h1>
</template>
</template>
<!--2.6 语法 -->
<template>
<template v-slot:header>
<h1> title </h1>
</template>
</template>
Vue3 不反对全局办法$set
起因:在 Vue3 $set
中曾经被移除。
解决方案:防止应用 $set
,能够在data()
定义或者赋值对象和数组的时候,确保对象属性曾经响应式解决。
<!-- 谬误示例 -->
<template>
<div v-for="(item,index) in list">
名字:{{item.name}},
性别:{{item.sex}},
</div>
</template>
<script>
export default {data() {
return {list: []
}
},
mounted() {this.list.push({name: '张三'})
// 在 vue3 中不失效
this.$set(this.list[0], 'sex', 'man');
}
}
</script>
<!-- 正确示例 -->
<template>
<div v-for="(item,index) in list">
名字:{{item.name}},
性别:{{item.sex}},
</div>
</template>
<script>
export default {data() {
return {list: []
}
},
mounted() {this.list.push({name: '张三', sex: 'man'})
}
}
</script>
生命周期 destroy 和 beforeDestroy,在 Vue3 更名为 unmounted 和 beforeUnmount
通过版本判断应用不同的办法名。
export const BEFORE_DESTROY = isVue3() ? 'beforeUmount' : 'beforeDestory';
export const DESTORY = isVue3() ? 'unmounted' : 'destory';
import {BEFORE_DESTROY} from '@/assets/utils/compat';
export default {[BEFORE_DESTROY]() {// 业务代码...}
}