背景
今年公司终于开始使用vue3框架,但面临着一个问题。公司的基础建设都是基于vue2的,并且目前vue2的项目还在使用,就想着是不是有什么方案能够解决vue2 和vue3的差异,于是发现了它"Vue-demi"。
过程还是经历了一些坎坷和纠结,基本上都是由于不看文档导致的,老是想投机取巧,事实证明都是弯路。
介绍
Vue-demi 是vue2 和 vue3的兼容解决方案,它的底层原理很简单是就是利用vue2和vue3的共同性,并且支持composition api,但不支持sfc(<template>、<script> 、<style>)原因是template在vue3下渲染函数和vue2不同,必须使用render 输出模版,要注意的是vue2 和 vue3的特效差异,提供了 isVue2、Vue2、isVue3、vue3 方案支持使用独有特性。
如果使用option Api 原理也是相同的,以下是列举了在使用setup 时的差异。
内容
调用全局方法
// vue2 采用的是
Vue.prototype.$message
// vue2 setup 使用
// ...
setup(props,refs){
refs.app.$message
}
// xxx.vue
// vue 3
app.appContext.config.globalProperties.$message
// vue3 setup
// ...
import { getCurrentInstance } from 'vue-demi'
const vm = getCurrentInstance()
vm.appContext.config.globalProperties.$message
//...
expose(object)
本来想使用setup + return render 的方式来更快的编写暴露给外部的变量函数,结果发现vue2 并没有expose
setup(props,{expose,slot}){
// expose(option) 是提供暴露给父级的api,但只在vue3可以使用
}
defineEmits 事件
声明事件的方式也有差别
import { defineEmits,isVue3 } from "vue-demi"
setup (props, ctx) {
if(isVue3){
defineEmits(["change"]); //父组件传来的方法
}
// vue2 直接使用,vue3 需要使用 vue3.defineEmits 声明,composition-api 没有此方法
ctx.emit("change")
}
render差异
在使用render时也需要到了问题,这也是从网上收集到的,一般的做法是抽离成兼容的函数处理
slot?.() 插槽
import { defineEmits,isVue3 } from "vue-demi"
//vue2 函数式插槽是组件
render(){
return h(tagName,{},this.$slots.default)
}
// vue3 函数式插槽是函数,需要执行
render(){
return h(tagName,{},this.$slots?.default())
}
//通用
render(){
return h(tagName,{}, isVue3?this.$slots?.default(): this.$slots.default )
}
h()构造函数
h(tagName:[string,any],option:object,children:array)
tagName vue2 可以直接传入组件的名称,但vue3不行,所抹平差异应该将组件传入,字符串只支持默认的标签
//vue2
h("el-button")
// vue3 vue2 都支持
import {ElButton} from "element-ui"
h(ElButton)
vue3 采用扁平化设计 attrs 和 evens 都在同一级,建议自定义h()
// vue3
h(tagName,{
maxlength:3,
src:"#" ,
onClick(){},
style:{}
})
//vue2
h(tagName,{
attrs:{ src:"#", maxlength:3 },
on:{
click(){}
},
style:{}
})
children 是子集内容,如果是内容直接使用标签,如果还有构造函数建议使用数组存,否则vue2时会丢失
h(tagName,options,"string ...") // 字符串正常显示,当是构造函数时丢失
h(tagName,options,[h("div")]) // 通用
v-model
在h() 不存在模版语句必须使用js实现
// setup(props,{emit}){
// vue2
h("input",{
props:{ value:props.value },
on{
input:(e)=>{ emit("input",e.target.value) }
}
})
// vue3 update:modelValue 事件不用声明
h("input",{
//...
value:props.value,
//...
"oninput"(e)=>{ emit("update:modelValue",e.target.value) }
},)
注意如果是非原生元素,应该是如下
// setup(props,{emit}){
// vue2
h("el-input",{
props:{ value:props.value },
on{
input:(e)=>{ emit("input",e) }
}
})
// vue3 v-model 时默认为modelValue
h(ElInput,{
//...
modelValue:props.value,
//...
"onUpdate:modelValue"(e)=>{ emit("update:modelValue",e) }
},)
组件获取
vue2 使用setup + ref 无法获得实例
//vue3
import { ref } from "vue-demi"
{
setup(){
const refELForm = ref()
}),
render(){
return h(elForm,{ ref:"refELForm" }})
})
}
// 以上写法vue2不支持,因为模板编译阶段和运行时阶段是分离的,所以在 setup 函数执行时,模板尚未被编译成可执行的代码
import { ref,getCurrentInstance ,onMounted} from "vue-demi"
{
setup(){
const that = getCurrentInstance()
onMounted(()=>{
// 注意保证实例是已经被创建的
console.log(that.refs.refELForm)
})
}),
render(){
return h(elForm,{ ref:"refELForm" }
})
})
}
项目参考 @geeboo/img-view - npm (npmjs.com))
感谢各位的阅读,有问题请指正:)