当前位置: 首页 > Web前端 > HTML5

uni-app黑魔法:运行在H5平台上的小程序自定义组件

时间:2023-04-05 01:44:07 HTML5

${styleCode}介绍早期的移动互联网,由于设备硬件性能的限制,流量以原生APP为主,iOS和Android是当时的两大平台。随着硬件和OS的升级,H5所能承载的体验也在逐步提升。为了提高开发效率、节省资源(代码复用)和热更新,Hybrid模式成为主流;而在轻应用、服务号Push等平台的帮助下,H5网页流量暴涨,成为第三大平台。2017年1月9日,微信发布小程序。经过三年的发展,在今年以“未完待续Beta”为主题的微信公开课PRO中,微信团队透露,2019年小程序日活跃用户数突破3亿,累计全年成交额达到8000亿,同比增长超过160%。看到小程序的惊人增长,我们有理由相信,中国特色小程序的互联网时代已经到来,微信小程序已经成为继iOS、Android、H5之后的第四大流量平台。拆分平台,为不同的平台编写相同的业务代码,很无聊。有抱负的程序员一直在探索代码重用的解决方案,HybridApp就是其中的代表。在如今这个小程序时代,对于同样基于WEB技术的H5和小程序来说,如何实现代码复用是很多前端工程师探索的方向。业界也有很多成熟的解决方案。从场景来看,大致可以分为三类:基于跨端框架,从头开发,一套代码,多平台发布,比如点云出品的uni-app,京东出品的uni-appAo-凸实验室。taro复用H5代码,将H5代码转换为在小程序环境中执行;适用于有H5平台积累,没有开发过小程序或小程序完善程度不高的开发者;美团的mpvue框架是解决这个问题的早期探索代表,但由于小程序不支持dom操作,mpvue适用于无dom操作的vue的H5代码转换;最近,微信官方推出的kbone也是为了解决“把web端的代码搬到小程序环境执行”的问题;但是,kbone相对于mpvue来说是进步了(当然也有新的性能缺陷),因为:kbone实现了一个adapter,在适配层模拟浏览器环境,这样web端的代码就可以无任何的跑起来小程序的变化。复用小程序代码,转换小程序代码运行在web环境;适合有小程序代码积累,没有开发过H5或者H5平台比较低的开发者;业界在这个方向上成熟的解决方案还比较少。uni-app近期支持H5平台小程序自定义组件的运行,是对上述第三种场景的探索。需求场景鉴于小程序低成本获客的特点,很多厂商选择先开发小程序,验证商业模式,再扩展到H5、App等其他平台。虽然开发者可以使用转换器将小程序代码转换为uni-app项目(或其他跨终端框架项目),快速实现多平台分发;但很多开发者并不敢轻易决定用跨终端版取代之前的线上版小程序版,毕竟线上版已经稳定运行了一段时间。普遍选择的方案是:让原生小程序版和uni-app跨端版并排运行一段时间,微信平台继续使用原生版,其他平台使用uni-app跨端终端版本;过一段时间验证uni-app版本稳定后,再用uni-app版本替换原生小程序版本。在这个并行期间,开发者需要同时维护微信原生和uni-app两个版本。对于新业务,需要编写两份逻辑相同的代码,重复劳动,成本重叠。如何提高?借助uni-app在H5平台运行微信小程序组件的特性,我们给出一种思路:开发者在原生小程序项目中以自定义组件的形式开发新服务,并优先推出小程序;将小程序组件的wxml/wxss/js/json文件复制到uni-app项目中,使用uni-app编译器和runtime保证小程序自定义组件在H5平台上的正确运行。这种方案的优点是:优先考虑小程序的开发。毕竟小程序已经上线了。现有用户可以复用小程序组件。新业务只需要开发一套代码。不仅通过自主开发的小程序组件降低了开发成本。开源的三方小程序组件,可以复制到uni-app项目中,在H5平台上运行。另外,有些公司的产品经理会要求不同的平台有不同的交互,但是核心业务逻辑是一样的。开发人员经常通过维护不同的项目来满足产品经理的需求。此时采用上述方案也可以满足多个项目复用相同业务逻辑的需求。事实上,uni-app此前已经支持小程序自定义组件运行到App平台。这对于积累了小程序组件或者优先考虑小程序的开发者来说是个好消息。一套业务组件可以快速跑到iOS、Android、H5、微信小程序这四大流量平台(其实QQ小程序平台也可以跑)。uni-app引用小程序组件演示uni-app项目中自定义组件的使用非常简单,分为三步:1.将小程序自定义组件复制到uni-app项目根目录下的wxcomponents文件夹下2.在pages.json中引入对应style->usingComponents的组件,如:{"pages":[{"path":"index/index","style":{"usingComponents":{"custom":"/wxcomponents/custom/index"}}}]}3.在页面中使用自定义组件,如:方案实现思路简单介绍下uni-app的多端分发原理。uni-app基于Vue.js运行时,页面文件遵循Vue.js单文件组件(SFC)规范。自然对H5的支持更好。发布到H5平台时,先通过vue-loader解析.vue文件,导出Vue。js组件选项对象,然后在运行时添加规范实现:Component:框架提供内置组件(view/swiper/picker等)的实现,保证平台UI和交互的一致性Interface:封装框架接口H5平台上,比如路由跳转Turn、showToast等界面交互生命周期:Vue.js的理念是一切都是组件,没有应用和页面的概念;framework需要创建application和page的概念,模拟onLaunch,onShow等hooksuni-app下发给applet平台的逻辑不一样,主要有两个任务:Compiler:splitthe.vue文件转换为wxml/wxss/js/json4原生页面文件运行时:Vue.js和小程序都是逻辑视图层框架,都具有数据绑定功能;运行时,实现Vue.js到小程序的数据同步,小程序到Vue.js的事件代理。它由4个文件wxml/wxss/js/json组成,有单独的组件规范(如Component构造器、Behaviors特性等)。因此,小程序自定义组件运行在H5平台上,可以借助uni-app已有的平台功能快速实现:编译阶段:将wxml/wxss/js/json这四个文件组合成.vue文件(类似于uni-app下发给小程序的逆向过程),然后调用uni-app发布H5平台的编译过程,通过vue-loader解析.vue文件,导出vue.js组件选项对象。编译生命周期:转换文件(mp2vue)小程序自定义组件发布到H5平台,编译过程主要有两个任务:将自定义组件的wxml/wxss/js/json四个文件组合,编译并将它们转换成.vue文件,即小程序转成vue,可以简称为mp2vue。通过vue-loader解析.vue文件,导出Vue.js组件选项对象。其中第2步为Vue.js项目的标准编译流程,略过;我们专注于描述步骤1。mp2vue将4个独立的wxml/wxss/js/json文件合并为一个.vue文件,组装成模板、脚本、样式三级结构。流程包括:wxml文件生成模板节点,同时完成指令、Event等模板语法转换js/json文件生成脚本节点,同时完成组件注册通过wxss文件生成样式节点,自动转换部分css兼容语法到.vue文件中。如果存在,则认为有小程序自定义组件,开始文件转换工作(uni-migration插件完成)://loadtheconverterconstmigrate=require('@dcloudio/uni-migration')//扫描wxcomponents目录constwxcomponents=path.resolve(process.env.UNI_INPUT_DIR,'wxcomponents')if(fs.existsSync(wxcomponents)){migrate(wxcomponents,false,{silent:true})//convertmp-weixinappletcomponents}然后开始wxml/wxss/js/json文件一个一个解析合并成一个.vue文件:file判断是否为组件const[jsCode,isComponent]=transformJsonFile(filepath+'.json',deps)options.isComponent=isComponent//转换wxml文件const[templateCode,wxsCode='',wxsFiles=[]]=transformTemplateFile(filepath+templateExtname,options)//转换wxss文件conststyleCode=transformStyleFile(filepath+style分机名、选项、部门)||''//转换js文件constscriptCode=transformScriptFile(filepath+'.js',jsCode,options,deps)//生成合并.vue文件的源代码return[`${commentsCode}${wxsCode}${styleCode}`,deps,wxsFiles]}更多详情,wxml文件转换为模板节点时,需要完成各种指令、事件等模板语法的转换,例如:小程序自定义组件Vue组件描述wx:ifv-if条件渲染wx:forv-for列表渲染bindtap@click元素的点击事件按照上面的流程转换了一个最小的自定义组件,结果如下:,并且依然保留了Component构造函数的写法,甚至其中的API依然以wx开头。这些都依赖于H5平台uni-app的runtime来解决,主要包括以下几个部分:组件构造器:解析小程序的各种组件Option配置,转换为Vue组件定义,包括灵活实现的差异,如独特的“组件所在页面的生命周期”行为特性:转换为Vue的混合(mixin)数据响应:在H5平台接口上实现setData和this.data.xx=yy数据通信机制API前缀:wx。xx可以在运行时通过代理机制自动替换成uni.xx。这个比较简单,Component的构造器uni-app在H5平台没有详细定义一个Component函数。执行完小程序的Component构造函数后,开始循环解析其属性,并将其转化为Vue组件属性。流程示意图代码如下:exportfunctionComponent(options){constcomponentOptions=parseComponent(options)componentOptions.mixins.unshift(polyfill)componentOptions.mpOptions.path=global['__wxRoute']initRelationsHandler(componentOptions)global['__wxComponents'][global['__wxRoute']]=componentOptions}导出函数parseComponent(mpComponentOptions){const{数据、选项、方法、行为、生命周期、观察者、关系、属性、pageLifetimes、externalClasses}=mpComponentOptionsconstvueComponentOptions={mixins:[],props:{},watch:{},mpOptions:{mpObservers:[]}}//开始逐个解析所有属性(关系,vueComponentOptions)parseProperties(属性,vueComponentOptions)parsePageLifetimes(pageLifetimes,vueComponentOptions)parseExternalClasses(externalClasses,vueComponentOptions)parseLifecycle(mpComponentOptions,vueComponentOptions)parseDefinitionFilter(mpComponentOptions,vueComponentOptions)//返回Vue组件returnvueComponentOptions}在这个过程中,需处理小程序自定义组件和Vue组件的属性对应和细节差异,比如小程序组件的生命周期:小程序自定义组件Vue/uni-appdescriptioncreatedonServiceCreated当触发小程序的创建时,可以访问子组件信息,但是无法访问Vue的创建,所以uni是必须的-app框架映射到其他时序(onServiceCreated)执行attachedonServiceAttached同上readymountedVue生命周期moved-Vue没有这个钩子,不支持detacheddestroyedVue生命周期小程序pageLifetimes(页面生命周期)的转换组件所在位置)在Vue中是没有的,需要映射到uni-app封装的页面生命周期:小程序自定义组件uni-app说明readyonPageShow页面显示时执行hideonPageHide页面显示时执行resizeonPageResize是hidden执行页面大小变化时Behaviors特性的实现过程,类似于Component的构造函数,不再数据响应Vue和小程序都有一套数据绑定系统,只是机制不同。比如在Vue系统中,数据赋值是这样的:this.a=1但是在小程序中,数据赋值方式是这样的:this.setData({a:1})//响应式this.data。a=2//Non-responsive另外,小程序和Vue在数据属性和观察者方面有很多不同。经过我们的评估,如果将小程序的数据响应使用直接映射到Vue系统,比较复杂,有性能压力。因此,uni-app按照微信的语法规范,在H5平台上独立实现了一套数据响应系统。//H5平台小程序setData的实现functionsetData(data,callback){if(!isPlainObject(data)){return}Object.keys(data).forEach(key=>{if(setDataByExprPath(key,data[key],this.data)){!hasOwn(this,key)&&proxy(this,SOURCE_KEY,key);}});this.$forceUpdate();//数据变化,强制视图更新(响应式)isFn(callback)&&this.$nextTick(callback);}在vm对象上挂载setData,可以通过这样的小程序调用。设置数据;同时,将数据绑定到data属性,支持this.data.xx访问方式。exportfunctioninitState(vm){constinstanceData=JSON.parse(JSON.stringify(vm.$options.mpOptions.data||{}))vm[SOURCE_KEY]=instanceData//挂载vm对象的setData方法实现小程序methodvm.setData=setDataconstpropertyDefinition={get(){returnvm[SOURCE_KEY]},set(value){vm[SOURCE_KEY]=value}}//小程序的使用,可以通过this.data访问Object.xx.defineProperties(vm,{data:propertyDefinition,properties:propertyDefinition})Object.keys(instanceData).forEach(key=>{proxy(vm,SOURCE_KEY,key)})}虽然数据响应是uni-app本身,但是渲染还是使用了vue框架的render函数。此时小程序规范中的this.data.xx需要和Vue规范中的this.xx保持一致,通过proxy实现://mp/polyfill/state/proxy.jsconstsharedPropertyDefinition={enumerable:true,可配置:true};functionproxy(target,sourceKey,key){sharedPropertyDefinition.get=functionproxyGetter(){returnthis[sourceKey][key]};sharedPropertyDefinition.set=functionproxySetter(val){this[sourceKey][key]=值;};Object.defineProperty(target,key,sharedPropertyDefinition);}这里只列出了主要步骤,中间涉及到很多细节;一些不能通过Vue扩展机制实现的功能需要在Vue.jsKernel源码中进行修改,比如H5平台的updateProperties支持、小程序wxs、externalClasses等功能,都需要自定义部分Vue。js运行时源代码。结语本文分享uni-app将微信小程序自定义组件发布到H5平台的实现思路,希望对大家有所启发。但这个方案,归根结底是为了解决多套项目并存时业务重复开发的问题。如果是白手起家,建议直接选择业界成熟的跨端框架,既可以维护一套代码,省去维护的精力,又可以依托框架成熟的生态(比如cross-端UI库和插件市场),基于成熟的轮子,快速完成业务的线上开发;uni-app框架代码,包括发布到H5平台的小程序组件代码,全部在github上开源。如果对本文逻辑有任何疑问,欢迎提交issue进行交流。