@qinyun
2018-08-29T18:31:59.000000Z
字数 2915
阅读 2476
未分类
anu小程序是去哪儿网推出的基于anujs的React转微信小程序方案。
近年来,涌现不少转换小程序的方案,mpvue、 mina、wept、wepy、 mpvue-wxparser、 taro,一方面说明小程序的确兴旺发达,另一方面也暴露了小程序自身的抽象能力不足,更体现这些补救工具也是各种槽点,因此才一直混战到现在。
所有工具的目的,都是优化开发体验,为小程序添加各种理所当然的支撑能力——比如说,css的工业化抽象(postcss,sass, less),npm支持(享受全世界的开源库),es6,es7语法糖,typescript的强类型约束,vue/react的组件开发,vuex/redux的状态管理……而这些,小程序统统没有,落后得如回到ie6的洪荒时代。
由于小程序长得像Vue(指它的template指令风格),因此早期的转译器都是Vue系的。而wept与mpvue带来的问题不比它们解决的问题少。于是社区继续造轮子。人们发现小程序的虚拟DOM机制其实是来自react,setData, forceUpdate API也是很好的证据。因此react风格的转译器兴起了,最出名的代表是taro,但taro是个早产儿,BUG非常多。
因此我们公司几经考虑后,自己开发自己的小程序工具。像之前的文章所言,这么多难点,社区上已经有对应的解决方案,剩下的问题是如何整合成一个顺手的框架。anu小程序背靠anujs这个实现精良的迷你框架,依仗群里的已有代码,经过着两个月的努力,披荆斩棘,臻于完美。下面向大家展示这伟大成果。
小程序的最大问题是没有组件继承机制,它所谓的自定义组件就是一个Component,不能指定父类。换言之,就是20年前的面向配置编程。因此大家都是设法引用vue/react这样广为流行的组件机制。其次,小程序的模板机制也非常弱,限制很多,虽然有点像vue,但它的数据都会经过JSON.stringify过滤,因此不能传函数,如何突破这个问题是难点。
我们看第一个问题,小程序的JS文件基本上分为4类,app.js,pages目录下的JS文件,components目录下的JS文件,及其他目录下的JS文件。为了方便区分,anu依次识别为App类,Page类与Componet类,它们都是转译器处理的对象。其他目录下的JS只会做拷贝,不转译。App类,Page类与Componet类都应该用React定义组件的方式实现的。
App类就是app.js这一个文件,用于抽取依赖,构建app.json。下面是用户编写的app.js,及转译后的app.js与app.json。app.json用户不需要编写。事实上,小程序的各种配置用的json,我们的转译器全包了,这样更符合我们react的开发方式。
Page类是定义在pages目录下的React组件,anu转义器会将它的es6 类风格变成React.miniCreateClass定义的类,因为es6 类转换成es5形式时,babel会添加这么多辅助方法,导到页面体积过大,这是对体积拮据的小程序来说是不可容忍的。
下面是pages/index/index.js转译的例子:
转译后,生成三个文件:
Componet类是components目录下的React组件。小程序有两种定义组件的方式,template组件与自定义组件。anu转译器会将Componet类转译成template组件,并且在组件更新时,收集它的props, state, context到Page类组件的props中。最后由Page类进行统一的setData,从而减少页面的刷新频率。
Animal组件转换成两个文件,留意它的wxml都是包在一个template标签中。
通过以React组件开发方式,我们把本来应该写两三个文件的繁锁开发方式,改成一个文件。当然,如果你引入了xxx.less, xxx.sass,anu小程序还会在对应目录下当成wxss文件。此外带来的好处还有,你不增加插件与配置的情况下,在原开发工具上(vscode, webstrom)享受语法高亮与提示。
第二个问题是模板数据的突破,之所以不用小程序后来推出的自定义组件机制,而是使用template标签,是因为template能全量接受上方传下来的所有数据。而自定义组件需要在properties对象定义了才能接受。这与React差距太多了。
小程序的Page类的wxml不是能使用函数来操作数据的,当然你可以用wxs,但wxs太鸡肋。因此我们的方案是在React的虚拟DOM中diff好,统一提交到Page类的props中。这就是anu小程序的双模板机制。它同时拥有被编译了并加料的JSX模板,及经过优化的wxml。用户在编写组组件的JSX时,与写普通的React程序没什么两样,但为了突破小程序的限制,我们在这个JSX上加入这么多随机数,它们是各种UUID。比如说,我们会在用户定义了onTap事件的标签上添加三个UUID。
转译后的第一个模板:
data-class-code对应的类的UUID, data-insance-code对应的是实例的UUID,data-tap-fn对应的事件UUID。onTap在JSX中还是原来的方法。
这是转译后的第二个模板,wxml用的模板。当我们跑小程序时,会先把所有react组件跑一次,理所当然,JSX(第一个模板)也跑起来。当它们跑起来了,事件回调就会存到实例上,实例就会存到类.然后我们点击wxml, 它运行的是dispatchEvent这个统一的委托者,它会从事件对象中找到target,然后再找到dataset,里面包含了{tapFn: '89136908', classCode: 'c5552349481165', instanceCode: "234324324" }。
然后我们就能通过类的UUID找到类,再通过实例的UUID从类中拿到实例,再通过事件的UUID从实例中拿到事件,最后触发事件。由于事件在JSX中已经bind了this与参数,因此就解决传参问题,不管这参数是简单类型还是复杂类型。像taro,它只能传简单类型,并且只能bind一次。而anu小程序,随着JSX的diff,会同步最新的数据。对于样式的单位处理与复杂的数据计算,涉及到函数问题,我们也是这么干。因此说anu小程序的双模板机制是一个聪明的实现,轻松绕开了小程序鳖足的设计。
对于mpvue最为诟病的问题,因为setData过于频繁导致性能底下的问题,anu是将所有数据更新上交到Page类进行setData。其他方面,我们实现对无状态组件的支持,支持并行构建用户代码,支持动态添加wx:key,支持智能省略wx:if分支(因为if分支已经在JSX中进行过滤一次,数据都已经剪枝),支持复杂if, for语法的转译,比市面上其他React转小程序更加强悍。
最后,欢迎大家试用: