vue-demi 兼容vue2、vue3的一些实践

1,798 阅读3分钟

背景

    今年公司终于开始使用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))

感谢各位的阅读,有问题请指正:)