6个实例带你解读TinyVue 组件库跨框架技术
本文分享自华为云社区《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 中引入响应函数
import { reactive, ref, watch, ... } from '@vue/composition-api'
在Vue 3 中引入响应函数
import { reactive, ref, watch, ... } from 'vue'
解决方案:通过在适配层暴露一个hooks变量,统一响应式函数的访问,代码如下
// adapter/vue2/index.js import * as hooks from '@vue/composition-api'
// adapter/vue3/index.js import * as hooks from 'vue'
// adapter/index.js export { hooks }
(2)VNode和 h 函数的差异:
在Vue 2.6中,渲染函数的 VNode 参数结构
{ staticClass: 'button', class: { 'is-outlined': isOutlined }, staticStyle: { color: '#34495E' }, style: { backgroundColor: buttonColor }, attrs: { id: 'submit' }, domProps: { innerHTML: '' }, on: { click: submitForm }, key: 'submit-button' }
在Vue 3 中,渲染函数的 VNode 参数结构是扁平的
{ class: ['button', { 'is-outlined': isOutlined }], style: [{ color: '#34495E' }, { backgroundColor: buttonColor }], id: 'submit', innerHTML: '', onClick: submitForm, key: 'submit-button' }
解决方案:通过在适配层暴露一个h函数,让Vue3框架也能支持Vue2的参数格式。这样就能统一h 函数的用法,同时让在Vue2时期开发的组件在Vue3框架下兼容运行。
// adapter/vue2/index.js const h = hooks.h
// adapter/vue3/index.js const h = (component, propsData, childData) => { // 代码有省略...... let props = {} let children = childData
if (propsData && typeof propsData === 'object' && !Array.isArray(propsData)) { props = parseProps(propsData) propsData.scopedSlots && (children = propsData.scopedSlots) } else if (typeof propsData === 'string' || Array.isArray(propsData)) { childData = propsData } return hooks.h(component, props, children) }
// adapter/index.js export { h }
(3)v-model的差异:
在Vue 2.6中,在组件上使用 v-model 相当于绑定 value 属性和 input 事件
<ChildComponent v-model="pageTitle" /> <!-- 会编译为: --> <ChildComponent :value="pageTitle" @input="pageTitle = $event" />
在Vue 3 中,v-model 相当于绑定了 modelValue 属性和 update:modelValue 事件
<ChildComponent v-model="pageTitle" /> <!-- 会编译为: --> <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
解决方案:通过Vue2中声明 model的option 选项,来自定义Vue2框架下v-model 的默认绑定 prop 和 event 。
defineComponent({ model: { prop: 'modelValue', // 默认值为 value event: 'update:modelValue' // 默认值为 input }, props: { modelValue: String } // ... })
(4)slots的差异:
在Vue 2.6中,有普通插槽 slots 和 作用域插槽 scopedSlots
// 普通插槽为对象,可以直接使用 this.$slots.mySlot
// 作用域插槽为函数,要按函数来调用 this.$scopedSlots.header()
在Vue 3 中,统一为 slots 函数的形式
// 将所有 scopedSlots 替换为 slots this.$slots.header()
// 将原有 slots 改为函数调用方式 this.$slots.mySlot()
解决方案:通过构建一个vm.$slots属性, 来统一2个框架中,访问slots的访问。
// adapter/vue2/index.js Object.defineProperties(vm, { // ...... $slots: { get: () => instance.proxy.$scopedSlots }, $scopedSlots: { get: () => instance.proxy.$scopedSlots }, })
// adapter/vue3/index.js Object.defineProperties(vm, { // ...... $slots: { get: () => instance.slots }, $scopedSlots: { get: () => instance.slots }, })
我们在vm下,还暴露了许多框架runtime层面上的组件属性,用于抹平跨Vue框架的差异。在开发跨框架组件时,要使用vm来访问组件,避免直接访问组件的instance。
// 创建一个Vue2 运行时的兼容 vm 对象 const createVm = (vm, _instance) => { const instance = _instance.proxy
Object.defineProperties(vm, { $attrs: { get: () => instance.$attrs }, $listeners: { get: () => instance.$listeners }, $el: { get: () => instance.$el }, $parent: { get: () => instance.$parent }, $children: { get: () => instance.$children }, $nextTick: { get: () => hooks.nextTick }, $on: { get: () => instance.$on.bind(instance) }, $once: { get: () => instance.$once.bind(instance) }, $off: { get: () => instance.$off.bind(instance) }, $refs: { get: () => instance.$refs }, $slots: { get: () => instance.$scopedSlots }, $scopedSlots: { get: () => instance.$scopedSlots }, $set: { get: () => instance.$set } })
return vm } // 创建一个Vue3 运行时的兼容 vm 对象 const createVm = (vm, instance) => {
Object.defineProperties(vm, { $attrs: { get: () => $attrs }, $listeners: { get: () => $listeners }, $el: { get: () => instance.vnode.el }, $parent: { get: () => instance.parent }, $children:{get:()=>genChild(instance.subTree)}, $nextTick: { get: () => hooks.nextTick }, $on: { get: () => $emitter.on }, $once: { get: () => $emitter.once }, $off: { get: () => $emitter.off }, $refs: { get: () => instance.refs }, $slots: { get: () => instance.slots }, $scopedSlots: { get: () => instance.slots }, $set: { get: () => $set } })
return vm }
(5)指令的差异:
Vue3的指令生命周期的名称变化了, 但指令的参数基本不变
解决方案:在开发指令对象时,通过补齐指令周期,让指令对象同时支持Vue2 和 Vue3
(6)动画类型的差异:
解决方案:在全局的动画类名文件中,同时补齐2个框架下的类名,让动画类同时支持Vue2 和 Vue3的Transition组件
// 此处同时写了 -enter \ -enter-from 的类名,所以它同时支持vue2,vue3的 Transition 组件。 .fade-in-linear-enter, .fade-in-linear-enter-from, .fade-in-linear-leave-to { opacity: 0; }
在构建TinyVue跨框架组件库的过程中,团队集中攻克了多个Vue框架间的关键差异点,其中六项尤为突出且具有代表性。
开发TinyVue跨框架组件库时,面对Vue2与Vue3的重要区别,我们确立了两个核心原则:一是“求同去异”,即在编写组件时选用两框架都支持的通用语法,如因Vue2不支持多根节点组件而统一采用单根节点设计;二是“兼容并包”,通过构建适配层隐藏框架间的差异,提供统一接口,无需开发者手动判断框架版本,这样他们可以更专注于逻辑开发。在指令对象和动画类名等细节方面,同样贯彻这一简化差异、广泛兼容的理念。
6个实例带你解读TinyVue 组件库跨框架技术的更多相关文章
- 参与开源之夏 x OpenTiny 跨端跨框架 UI 组件库贡献,可以赢取奖金🏆!这份《OpenTiny 开源贡献指南》请收好🎁!
大家好,我是 Kagol. 近期有几位朋友在 OpenTiny 技术交流群里询问我们在开源之夏(OSPP)的项目,希望能提前做一些准备工作. 这里给大家简单介绍下开源之夏. 开源之夏是由中科院软件所& ...
- react 项目实战(十)引入AntDesign组件库
本篇带你使用 AntDesign 组件库为我们的系统换上产品级的UI! 安装组件库 在项目目录下执行:npm i antd@3.3.0 -S 或 yarn add antd 安装组件包 执行:npm ...
- OpenTiny 跨端、跨框架组件库升级TypeScript,10万行代码重获新生
摘要:一份精心准备的<JS项目改造TS指南>文档供大家参考,顺便介绍TS 基础知识和 TS 在 Vue 中的实践. 本文分享自华为云社区<历史性的时刻!OpenTiny 跨端.跨框架 ...
- 华为云 OpenTiny 跨端、跨框架企业级开源组件库项目落地实践直播即将开启!
大家好,我是 Kagol,公众号:前端开源星球. "你们这个产品怎么只能在电脑上适配呀?我想在手机上看都不行,太麻烦了!!" "你们这个产品看起来太简单了,我想要@@功能 ...
- 从零开发一款自己的小程序UI组件库(一)
写在前面:有开发过小程序的朋友肯定知道组件化开发的特性,高内聚与低耦合.使用已有的UI组件库,诸如:vantUI-weapp.minUI-weapp等UI组件库的诞生使我们的开发速度大大的加快,丰富的 ...
- 京东 Vue3 组件库支持小程序开发啦!
源码抢先看: https://github.com/jdf2e/nutui NutUI 3.0 官网:https://nutui.jd.com/3x/#/ 小程序多端适配 设计初衷 在跨端小程序的开发 ...
- 基于Docker的TensorFlow机器学习框架搭建和实例源码解读
概述:基于Docker的TensorFlow机器学习框架搭建和实例源码解读,TensorFlow作为最火热的机器学习框架之一,Docker是的容器,可以很好的结合起来,为机器学习或者科研人员提供便捷的 ...
- Jmeter Web 性能测试入门 (四):一个小实例带你学会 Jmeter 脚本编写
测试场景: 模拟并发100个user,在TesterHome 站内搜索VV00CC 添加线程组 添加HTTP信息头管理器 添加HTTP Sampler 填写HTTP Sampler中的信息 添加监听器 ...
- Vite+TS带你搭建一个属于自己的Vue3组件库
theme: nico 前言 随着前端技术的发展,业界涌现出了许多的UI组件库.例如我们熟知的ElementUI,Vant,AntDesign等等.但是作为一个前端开发者,你知道一个UI组件库是如何被 ...
- 如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
1.前言 关于微信内部正在使用的网络层封装库Mars开源的消息,1个多月前就已满天飞(参见<微信Mars:微信内部正在使用的网络层封装库,即将开源>),不过微信团队没有失约,微信Mars ...
随机推荐
- C# 委托(delegate)本质理解
代码如下,很简单 namespace Delegate { class Program { delegate void SayHi(); void SayHi_1() { Console.WriteL ...
- SQLSERVER 的表分区(水平) 操作记录2
1 ----(非原创只是自己整理记录!!!)---------------原文地址: https://www.cnblogs.com/libingql/category/184251.h ...
- 简直了,被“Java并发锁”问题追问到自闭...
故事 地铁上,小帅双目空洞地望着窗外...绝望,发自内心地感到绝望... 距离失业已经过去两个月了,这是小帅接到的第四次面试邀请."回去等通知吧...",简简单单的六个字,把小帅的 ...
- Postman模拟浏览器网页请求并获取网页数据
本文介绍在浏览器中,获取网页中的某一个请求信息,并将其导入到Postman软件,并进行API请求测试的方法. Postman是一款流行的API开发和测试工具,它提供了一个用户友好的界面,用于创 ...
- 04 jQuery遍历器
04 jQuery遍历器 如果jQuery一次性选择了很多元素节点. 而我们又希望能拿到每一个元素中的相关信息. 此时可以考虑用jQuery的遍历器来完成对元素的循环遍历. 例如: <!DOCT ...
- #构造#CF891B Gluttony
题目 有一个长度为 \(n\) 的数列 \(a\),数字互不相同, 现在要打乱这个数列,设其为 \(b\), 使得 \(a\) 的任意真子序列与对应的 \(b\) 的任意真子序列的数字和不同 输出任意 ...
- #dfs,trie#洛谷 4341 [BJWC2010]外星联络
题目 分析 首先时间复杂度\(O(n^2)\)可过,统计子串个数可以用trie或者更高级的方法 可以枚举所有的后缀,然后建一个trie,这样这个trie就可以记录全部的子串 关于字典序排序,深搜的时候 ...
- Solr安全控制,开启基本身份验证
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 背景:solr默认安装没带权限控制,每次进入页面直接操作都有点裸奔的感觉. 一.SolrCloud集群模式 说明:要使用基本身份验证您必 ...
- 难道AI不知道tnsnames.ora的instance_name配置吗?
事情是这样,给某客户培训构建hands-on实验环境时,因测试环境有限,在同一环境做了一套ADG环境: 数据库是单实例,版本19.21,使用了多租户选件: 其中一个测试的PDB,名为demo1,其中建 ...
- 开源数据库OpenGauss的SQL解析源码分析
开源数据库 OpenGauss 的 SQL 解析源码分析 OpenGauss 数据库体系概述 openGauss 是关系型数据库,采用客户端/服务器,单进程多线程架构:支持单机和一主多备部署方式,同时 ...