6个实例带你解读TinyVue 组件库跨框架技术

打印 上一主题 下一主题

主题 573|帖子 573|积分 1723

本文分享自华为云社区《6个实例带你解读TinyVue 组件库跨框架技术》,作者: 华为云社区精选。
在DTSE Tech Talk 《 手把手教你实现mini版TinyVue组件库 》的主题直播中,华为云前端开发DTSE技术布道师阿健老师给开发者们展开了组件库跨框架的讨论,同时针对TinyVue组件库的关键技术进行了剖析,并通过项目实战演示了一份源码编译出2个不同Vue 框架的组件。末了针对框架间的差异,也给出了相应的技术方案,帮助开发者们实战完成组件库跨框架。
直播链接:https://bbs.huaweicloud.com/live/DTT_live/202404171630.html
一、手把手带你实现mini 版 TinyVue

当前实现组件库的跨框架技术,是提升Web页面开发服从与应用灵活性的紧张手段。本次直播的实战环节,用300行代码模拟了 TinyVue 组件库的跨框架实现,开发者可以在mini 版组件库中,复现跨框架及多端适配两大功能。同时通过本期的实操环节,也给开发者呈现一个明白且详尽的实现流程,帮忙大家更好的理解并掌握跨框架技术并运用到实际工作中。
具体源码可参考: https://github.com/opentiny/mini-tiny-vue

二、为什么要实现组件库跨框架呢?

现在,Vue拥有Vue2和Vue3两大主要分支,它们在开发上并不兼容。Vue2还可以进一步细分为2.6及之前的版本和Vue2.7这两个小分支,其中Vue2.7作为2.6与Vue3之间的过渡版本,在开发上起着桥梁作用。
对于现有项目来讲,如果迁徙到Vue3,难免存在API及组件功能不同步的情况,因此迁徙过程将存在一定的本钱及风险。而在当前的Vue生态中,诸如Antdesign和Element等着名组件库都推出了支持Vue2和Vue3的组件。然而这些官网文档和API却并不通用,这意味着实际上是提供了两个独立的组件库来实现跨框架支持的。

作为致力于实现跨框架的TinyVue组件库,旨在实现跨不同版本的Vue框架兼容性,其独特之处在于接纳单份源代码策略,通过智能编译技术,能够同时生成实用于Vue 2.6、2.7版本以及Vue3版本的组件包。这意味着开发者只需维护同一个官方网站,并提供一套标准化的API接口,即可满足多版本Vue用户的需求。这种计划有效地减少了TinyVue组件库的维护本钱和未来技术迁徙的风险。
三、关键技术剖析

首先以一个button组件为例,组件的左上部门是模板,作为组件的入口,它集成了适配层、renderless逻辑以及theme样式(此处暂不涉及theme部门)。值得注意的是,组件内部并未包含任何逻辑代码,全部逻辑均被抽离至renderless中,这里可以按照下图所示观察其调用关系。


  • 从vue文件(即组件的入口文件)开始,引入了适配层中的setup函数和无状态的renderless函数。setup函数的调用过程中,将包含状态的props和context,以及无状态的纯函数renderless一并传入。
  • 然后进入setup函数内部,适配层中的tools函数会构造一个对象,用于抹平框架之间的差异,并将该对象通报给renderless函数。这样,在renderless函数中,可以放心地引用该对象,而无需担心组件是在vue2照旧vue3情况下运行。
  • 接下来调用纯函数renderless。它为每个组件构造一个与当前组件相干联的state和api,这些都是有状态的值。随后,这些状态值被返回给适配层。
  • 末了适配层将这些状态值通报给模板进行绑定。具体而言,state被绑定到模板的数据值上,而api则被绑定到模板的变乱上。
团体来看,调用过程就像一个管道,数据从模板开始流动,颠末逻辑处理,再流回到模板上。在这个过程中,它流经的适配层奇妙地抹平了框架之间的差异,正是TinyVue跨框架的精妙所在。
四、怎样解决框架差异统一,实现跨框架?

1、框架间的差异是什么?

Vue3是一次全新的框架升级,所以它的语法以及内部实现,都发生了很大的变化,这些是在开发跨框架组件库时必须考虑的题目。而在长期的跨框架组件库的开发中,可能会遇到众多的框架差异,具体可以将这些差异归结为2大类:
(1)框架对外差异,直接影响到模板的开发以及某些语法。例如:


  • 模板语法差异
  • 生命周期名称变化
  • 移除了变乱修饰符、过滤器、消息订阅
  • v-model 语法糖差异
  • 指令,动画组件的差异
(2)框架内部差异,主要是Vue runtime层面的实现差异。在开发跨框架组件过程中,需要访问组件内部某些变量时可能会遇到,例如:


  • 组件实例的差异
  • Vnode结构的差异
  • 移除了$children, $scopedSlots等
2、 框架差异及应对方案

(1)相应式函数引入包差异:

在Vue 2.6 中引入相应函数
  1. import { reactive, ref, watch, ... } from '@vue/composition-api'
复制代码
在Vue 3 中引入相应函数
  1. import { reactive, ref, watch, ... } from 'vue'
复制代码
解决方案:通过在适配层暴露一个hooks变量,统一相应式函数的访问,代码如下
  1. // adapter/vue2/index.js
  2. import * as hooks from '@vue/composition-api'
  3. // adapter/vue3/index.js
  4. import * as hooks from 'vue'
  5. // adapter/index.js
  6. export { hooks }
复制代码
(2)VNode和 h 函数的差异:

在Vue 2.6中,渲染函数的 VNode 参数结构
  1. {
  2.   staticClass: 'button',
  3.   class: { 'is-outlined': isOutlined },
  4.   staticStyle: { color: '#34495E' },
  5.   style: { backgroundColor: buttonColor },
  6.   attrs: { id: 'submit' },
  7.   domProps: { innerHTML: '' },
  8.   on: { click: submitForm },
  9.   key: 'submit-button'
  10. }
复制代码
在Vue 3 中,渲染函数的 VNode 参数结构是扁平的
  1. {
  2.   class: ['button', { 'is-outlined': isOutlined }],
  3.   style: [{ color: '#34495E' }, { backgroundColor: buttonColor }],
  4.   id: 'submit',
  5.   innerHTML: '',
  6.   onClick: submitForm,
  7.   key: 'submit-button'
  8. }
复制代码
解决方案:通过在适配层暴露一个h函数,让Vue3框架也能支持Vue2的参数格式。这样就能统一h 函数的用法,同时让在Vue2时期开发的组件在Vue3框架下兼容运行。
  1. // adapter/vue2/index.js
  2. const h = hooks.h
  3. // adapter/vue3/index.js
  4. const h = (component, propsData, childData) => {
  5.   // 代码有省略......
  6.   let props = {}
  7.   let children = childData
  8.   if (propsData && typeof propsData === 'object' && !Array.isArray(propsData)) {
  9.     props = parseProps(propsData)
  10.     propsData.scopedSlots && (children = propsData.scopedSlots)
  11.   } else if (typeof propsData === 'string' || Array.isArray(propsData)) {
  12.     childData = propsData
  13.   }
  14.   return hooks.h(component, props, children)
  15. }
  16. // adapter/index.js
  17. export { h }
复制代码
(3)v-model的差异:
在Vue 2.6中,在组件上使用 v-model 相称于绑定 value 属性和 input 变乱
  1.   <ChildComponent v-model="pageTitle" />
  2.   
  3.   <ChildComponent :value="pageTitle" @input="pageTitle = $event" />
复制代码
在Vue 3 中,v-model 相称于绑定了 modelValue 属性和 update:modelValue 变乱
  1.   <ChildComponent v-model="pageTitle" />
  2.   
  3.   <ChildComponent :value="pageTitle" @input="pageTitle = $event" />
复制代码
解决方案:通过Vue2中声明 model的option 选项,来自定义Vue2框架下v-model 的默认绑定 prop 和 event 。
  1. defineComponent({
  2.   model: {
  3.     prop: 'modelValue', // 默认值为 value
  4.     event: 'update:modelValue' // 默认值为 input
  5.   },
  6.   props: {
  7.     modelValue: String
  8.   } // ...
  9. })
复制代码
(4)slots的差异:
在Vue 2.6中,有普通插槽 slots 和 作用域插槽 scopedSlots
  1. // 普通插槽为对象,可以直接使用
  2. this.$slots.mySlot
  3. // 作用域插槽为函数,要按函数来调用
  4. this.$scopedSlots.header()
复制代码
在Vue 3 中,统一为 slots 函数的形式
  1. // 将所有 scopedSlots 替换为 slots
  2. this.$slots.header()
  3. // 将原有 slots 改为函数调用方式
  4. this.$slots.mySlot()
复制代码
解决方案:通过构建一个vm.$slots属性, 来统一2个框架中,访问slots的访问。
  1. // adapter/vue2/index.js
  2.   Object.defineProperties(vm, {
  3.      // ......
  4.     $slots: { get: () => instance.proxy.$scopedSlots },
  5.     $scopedSlots: { get: () => instance.proxy.$scopedSlots },
  6.   })
  7.    // adapter/vue3/index.js
  8.    Object.defineProperties(vm, {
  9.     // ......
  10.     $slots: { get: () => instance.slots },
  11.     $scopedSlots: { get: () => instance.slots },
  12.   })
复制代码
我们在vm下,还暴露了许多框架runtime层面上的组件属性,用于抹平跨Vue框架的差异。在开发跨框架组件时,要使用vm来访问组件,制止直接访问组件的instance。
  1. // 创建一个Vue2 运行时的兼容 vm 对象
  2. const createVm = (vm, _instance) => {
  3.   const instance = _instance.proxy
  4.   Object.defineProperties(vm, {
  5.     $attrs: { get: () => instance.$attrs },
  6.     $listeners: { get: () => instance.$listeners },
  7.     $el: { get: () => instance.$el },
  8.     $parent: { get: () => instance.$parent },
  9.     $children: { get: () => instance.$children },
  10.     $nextTick: { get: () => hooks.nextTick },
  11.     $on: { get: () => instance.$on.bind(instance) },
  12.     $once: { get: () => instance.$once.bind(instance) },
  13.     $off: { get: () => instance.$off.bind(instance) },
  14.     $refs: { get: () => instance.$refs },
  15.     $slots: { get: () => instance.$scopedSlots },
  16.     $scopedSlots: { get: () => instance.$scopedSlots },
  17.     $set: { get: () => instance.$set }
  18.   })
  19.   return vm
  20. }
  21. // 创建一个Vue3 运行时的兼容 vm 对象
  22. const createVm = (vm, instance) => {
  23.   Object.defineProperties(vm, {
  24.     $attrs: { get: () => $attrs },
  25.     $listeners: { get: () => $listeners },
  26.     $el: { get: () => instance.vnode.el },
  27.     $parent: { get: () => instance.parent },
  28.     $children:{get:()=>genChild(instance.subTree)},
  29.     $nextTick: { get: () => hooks.nextTick },
  30.     $on: { get: () => $emitter.on },
  31.     $once: { get: () => $emitter.once },
  32.     $off: { get: () => $emitter.off },
  33.     $refs: { get: () => instance.refs },
  34.     $slots: { get: () => instance.slots },
  35.     $scopedSlots: { get: () => instance.slots },
  36.     $set: { get: () => $set }
  37.   })
  38.   return vm
  39. }
复制代码
(5)指令的差异:
Vue3的指令生命周期的名称变化了, 但指令的参数基本稳定

解决方案:在开发指令对象时,通过补齐指令周期,让指令对象同时支持Vue2 和 Vue3

(6)动画类型的差异:

解决方案:在全局的动画类名文件中,同时补齐2个框架下的类名,让动画类同时支持Vue2 和 Vue3的Transition组件
  1. // 此处同时写了 -enter \  -enter-from 的类名,所以它同时支持vue2,vue3的 Transition 组件。
  2. .fade-in-linear-enter,
  3. .fade-in-linear-enter-from,
  4. .fade-in-linear-leave-to {
  5.   opacity: 0;
  6. }
复制代码
在构建TinyVue跨框架组件库的过程中,团队集中攻克了多个Vue框架间的关键差异点,其中六项尤为突出且具有代表性。
开发TinyVue跨框架组件库时,面对Vue2与Vue3的紧张区别,我们确立了两个焦点原则:一是“求同去异”,即在编写组件时选用两框架都支持的通用语法,如因Vue2不支持多根节点组件而统一接纳单根节点计划;二是“兼容并包”,通过构建适配层隐藏框架间的差异,提供统一接口,无需开发者手动判定框架版本,这样他们可以更专注于逻辑开发。在指令对象和动画类名等细节方面,同样贯彻这一简化差异、广泛兼容的理念。
点击关注,第一时间了解华为云奇怪技术~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表