一、方案背景
长期以来 APP、H5、小程序等各个端的定位和发展历程都不一样,各端技术栈差异性也较大,基于成本和效率考虑并不追求各端一致性,结果就是各端真的就渐行渐远了。
移动端增量红利越来越少的情况下,产品这边逐渐追求各端的产品体验一致性,多端同时上的需求越来越多,但是由于技术上割裂较大,工时基本都会按端加倍,开发成本奇高,迫切的需要一套减少多端开发成本的方案。
二、方案调研
开始之前我们对业界现有的一些跨端方案进行了简单的调研和了解。
通过对比目前的多端开发主要有以下几个大方向:
对 IOS 和 ADR 的 native 端 APP 的适配
中国特色的针对各小程序平台的适配
都希望可以兼顾到对 H5 的转换
而实现方向上看起来五花八门,但总体思路上主要还是两个大方向:
提供自定义 DSL 静态编译转化成目标源代码(运行体验好)
运行时适配兼容(开发体验好)
这两种方式的优缺点都非常明显(相关框架和方案对比文章都较多,可自行详细了解)。目前社区基本都想在运行上努力,开发当然要自己爽啊,实际体验下来除了在微信小程序上性能问题较大之外,也没啥大毛病。
taro 作为较早的多端方案,适配性和兼容性都不错,自定义 DSL,JSX 由于其转译复杂性限制也较多。
taro-next 和 remax 都是运行时适配的方案,而 remax 的口号是 “使用真正的 react 构建小程序” (好好体会,后面要考)。
Chameleon 是号称兼容性和运行效率上都比较高的,编译和运行时适配的技术都用到了,不过由于他是支持的 weex 就没做深入尝试了。
其他像 alita,KBone,react-native-web 都在自己的小领域内能较好的运行。
三、方案介绍
1、解决思路
调研完发现上面的方案其实并不能很好的直接解决我们的问题:
Qunar 整体技术栈以 react 为主,几年前已将 APP 整体迁移到 RN 技术栈上,解决了 IOS 和 ADR 的 2 端问题。APP 的体验和开发是最重要的,不能为了多端用新的 DSL 语法来限制对 RN 技术的使用,降低 RN 的性能和开发体验。基于现状,其实我们想要的是:能直接将 RN 转换到 H5 和各小程序平台的多端方案,可惜没有。
不能直接找到一次将 RN 运行到多端的方案,其实如果能分别解决 RN 到 H5 和 RN 到小程序的问题也是能达到目标的,分析前面的跨端方案还是有一定可行性的。
2、RN 到 H5
react-native-web 是 Twitter 开源的可将 RN 代码运行在 H5 上的方案,而且他们 H5 端的官网就是直接用 RN 转出来的,自信到连自己主站都敢用的方案肯定是要尝试一下的,于是出去看了下线上效果:
发现保真度和体验还可以,对该方案的实现原理进行了解之后,觉得 H5 可行,后续到小程序的方案也深受启发。
3、react-native-web 原理
先看一行 RN 代码:
再看 react-native-web 的源代码:
使用方式:
他的方案其实挺简单粗暴的,把 RN 的组件和 API 都用 H5 实现适配一遍,适配其行为和默认样式,在打包的时候使用 webpack 的别名机制将用到的组件替换成 react-native-web 里的对应组件。
简单的方案实现起来工作量还是挺大的,所以 react-native-web 里也大量的直接使用了 react-native 的源代码,谁让他们都是 JS 呢。
既然到 H5 可以这么简单粗暴,那到小程序是否可以呢?
4、RN 到小程序
我们知道小程序都有一套自己的 DSL 语言,而且与常规前端不同的是他将逻辑层和视图层完全隔离在了两个线程里,两边数据和对象都不共享,只能通过 Native 层来传递数据通讯。
RN 广义上来说也是一样的双线程机制,不过 RN 的渲染是用的 native,小程序用的是 webkit。
RN 是完全基于 React 语言的,比小程序的 DSL 要灵活复杂得多,要把 React 语法和 JSX 编译成一个子集的话困难性还是很大的。
这个时候我们想到了 remax:使用真正的 react 构建小程序。
如果我们像 react-native-web 一样,把 react-native 组件用 remax 来实现一遍,是不是就可以将 RN 运行到小程序上了呢?
5、remax 实现原理
这部分网上有详细的原理介绍,这里只简单说一下:
react 在内部做完 VirtualDOM 的更新后最终都要调用宿主环境的更新方法(add remove)来更新界面,为了更好地对接各个平台 react 提供了 react-reconciler 库来传入真正宿主元素的更新方法(HostConfig)。基于这个库可以方便的实现出 ReactDOM、RN 等 react 渲染层。remax 就是基于该组件实现的一个 react 的渲染层,不过小程序里不能直接去更新界面,所以 reamx 会把更新收集起来,然后生成一个更新数据通过小程序的 setData 触发渲染层的更新。然后在小程序渲染层实现一个通用模板来渲染这些数据。
这个方案带来的好处和缺点,在微信小程序里的性能限制下面 remax 和 taro next 的原理介绍里都讲得很透彻,这里就不在复述了。
remax 原理介绍:
https://juejin.im/post/6844904131157557262
taro next 也采用了类似的原理:
https://juejin.im/post/6844904036743774216
6、RN 到小程序组件库
基本原理同 react-natvie-web,只不过在对 RN 组件的具体实现上采用直接使用对接到 remax 组件上的方式。看代码例子的话目录结构等是不是都和 react-native-web 很像。
四、最终多端方案
最终经过调研和尝试,我们选择了按平台用不同的方案进行转换的方案:
H5 上直接用 react-native-web 进行转换
到小程序端使用 reamx 组件实现一套 RN 的组件库,借用 remax 来适配到多端
借助 webpack 的特性来实现针对不同平台的打包
qrn-mirror 就是前面提到的针对 RN 组件到小程序的组件适配库,使用 remax 实现。
我们的主要工作量集中在 qrn-mirror 层对 RN 组件的适配,借助现有开源方案小成本的实现了这个事情。
当然在实现小程序组件的时候我们才理解 remax 只说了 “使用真正的 react 构建小程序” , 从来没说过支持多端一致的,这差别你体会体会……不过幸运的是 remax 确实也通过 remax/one 提供了多个多端兼容的基础组件。当然我们最终还是选择继续使用 remax 来开发了组件库,谁让他是完整支持 react 的呢,另一个我们也不追求一次把所有小程序端都适配了。
五、多端动态方案
前面实现了将 RN 代码在多端运行的机制,但是在实际迁移中并不是所有的 RN 特性小程序和 H5 都能支持,产品和交互本身也有差异,这时候还是需要对各端做差异化和降级的。
如何做到多态其实 webpack 都已经替我们做好了,我们只需要用和定好规范就好了。
1、文件内小范围的多态
借助 webpack 的 tree shaking 和 webpack.DefinePlugin 插件,可实现按端执行逻辑且不出现冗余代码。
2、文件组件级别的多态
受益于 react 良好的组件化机制,只要我们按文件导出同样 API 的 react 组件,可以无缝的针对多端做替换。remax 也能做到自动识别小程序的原生组件。而我们只需要根据平台动态修改 webpack 查找文件后缀的优先级顺序就好了。
最终打包时在 H5 端使用 index.web.js 内导出的兼容组件,其他端用 index.js 里导出的组件。
六、效果展示
最终我们实现了一个开发工具,在规范 RN 工程目录结构的情况下可以一键转换出 H5 和小程序的版本。目前已经在低价机票,机票订单详情,公共订单列表等项目中进行了应用。后续也希望在主流程和更多新项目中直接使用。
七、后续计划
这次我们只分享了整个方案的大致思路,后续会逐步分享一些我们在实践过程中踩的一些坑和应对方案。
第三方不侵入打包过程介绍
和 nanachi 的环境整合方案
react,react-native-web, remax 的版本对应和锁定问题
小程序不支持全局 global 对象的问题
小程序中 RN 动画解决方案
小程序中的渲染性能优化
stylesheet 样式解决方案
多平台工程的工程结构限制
库 Size 大小分析优化过程和解决方案
头图:Unsplash
作者:冯地木
来源:Qunar 技术沙龙 - 微信公众号 [ID:QunarTL]
转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论