微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

去哪儿网React转微信小程序实践

2018-08-29 18:41 司徒正美
作者|司徒正美
编辑|覃云

本文已获作者授权转载,原文请戳链接:https://zhuanlan.zhihu.com/p/42788287

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 转小程序更加强悍。

最后,欢迎大家试用:

https://github.com/RubyLouvre/anu/tree/master/packages/cli

  活动推荐

ArchSummit 全球架构师峰会将于 12 月 7-8 日在北京国际会议中心举办,会议专题聚集了微服务金融架构、微服务架构、数据基础平台建设、短视频架构、区块链、信息隐私安全等话题。邀请了阿里巴巴、Netflix、百度、LinkedIn 等公司的技术专家来分享。

大会 7 折报名中,立减 2040 元,有任何问题欢迎咨询票务经理 Lachel- 灰灰,电话 / 微信:17326843116。


 
前端之巅 更多文章 专访尤雨溪:先别管4.0了,Vue CLI重构了解一下 Chrome十周年版更新了,你第一次用它是什么时候? 前端要凉?微软开源Sketch2Code,草图秒变代码 JS可以写操作系统?Windows 95被装进Electron App JavaScript中Array方法的正确打开方式
猜您喜欢 小众App的逆袭:看看网易彩票如何承载丁磊的梦想 你不需要那些知识 大佬怎么抓才能不死? Python | 获取Android设备信息的轻量级框架 勒索病毒爆发的前后,第 5 期技术微周刊出炉了