组内对于 React Native 的实践已经快一年了,我们组主要负责的是美团外卖 M 端的前端业务,涵盖了美团外卖的 CRM、供应链、合同和结算等系统,我们的用户主要是美团的 BD,也就是广大的地推团队,他们是贵司的制胜法宝,我们是他们的贴心小棉袄。
为了更好的服务好广大的兄弟姐妹,满足移动化办公的需求,在 2015 年的时候上线了一个应用--蜜蜂。蜜蜂的第一个版本是通过 webview 作为载体,嵌入移动端页面,但是上线一段时间后,发现这种方案存在着卡顿、白屏以及流量消耗较大等问题。
为了进一步的改善应用的使用体验并且集成更多 Native 的功能,产品和技术一同推动了蜜蜂的整体改版。现在蜜蜂整体都建立在 React Native 上,iOS 的 Crash 率也控制在 0.8% 以下,Android 的 Crash 因为在华为的手机上存在一个厂商问题会稍微偏高,
本文将总结和回顾作为一个纯 web 前端团队使用 React Native 完成一个应用的整个过程。
技术选型
对于前端工程师写一个应用的技术方案,在很早以前就有了,例如大家比较熟悉的 PhoneGap、NativeScript 等,那么怎么选择一个对于业务合适的方案呢?在进行正式开始前,我们调研了 PhoneGap、ionic、React Native、NativeScript 和 Titanium,
由于当我们开始做的时候 weex 还没有正式推出,也就没有对 weex 做过多的调研。
- PhoneGap : PhoneGap is a free and open source framework that allows you to create mobile apps using standardized web APIs for the platforms you care about.
- ionic: Ionic is the beautiful, open source front-end SDK for developing hybrid mobile apps with web technologies.
- React Native: React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScrip and React.
- NativeScript: Build truly native apps with JavaScript Develop iOS and Android apps from a single code base
- Titanium: Write in JavaScript, run native everywhere Build native cross-platform apps at the speed of mobile—with no hybrid compromises.
对于 PhoneGap 和 ionic 从某种角度来说依然是和原有的蜜基础架构相似,因此依然不能达到我们对于原生 App 交互和 API 的期许。下图中对比了PhoneGap 和 ionic。
后面剩下的三个 React Native、NativeScript、Titanium。
- NativeScript 的期许是能够达到各个平台 100% 的复用代码,这个所带来的可能问题会是说为了兼容而兼容,带来系统性能上的优化。NativeScript 现在的版本为 1.5, GitHub 上的 Star 数为接近 5000, 完全开源,有许多已有发布的应用都有用到 NativeScript, Google 上的搜索结果也较多。但是 NativeScript 的维护团队并不是很壮大。
- React Native 的期许是学习一次,多处使用。它并不强调代码在各个端的复用,因此可以尽可能地保留各个平台的本身天然属性,因此对应用的性能是有所保障的。React Native 的开发维护团队来自 Facebook,并且社区活跃度较高。不足的点在于 React Native 现在的版本还不怎么稳定,蜜蜂第一个版本调研时为 0.14 版本。
- Titanium 的期许是 Write once, adapt everywhere,这一点和 React Native 有一些相似,并不期望已一套代码通行天下。会对不同的平台做出兼容性的处理。Titanium 已经出来了比较久了,有对应的 Studio 和统计等,但是有一个关键的问题,他们家是收费的,而且社区也不是特别活跃。
基于上述的调研,因此蜜蜂选择了 React Native 做为技术架构,来优化 App 的体验和性能。
开发模式与平台化
技术选型的确定,只是走了万里长征的第一步,接下来应用的架构以及应用从开发到上线的整个流程都是我们需要进行考虑的。
项目结构
在项目结构这个维度上,对于 RN 来说,我们更多的还是将其看做一个容器,这个容器和浏览器不同的点在于,我们可以通过开发 RN 的原生组件对其功能进行扩展。业务编写的前端同学只需要关注在 React 代码的编写,不需要关注过多 Native 的事情,这样大家的精力还是放在前端代码的编写上,而不会过于分散精力。
整体来说蜜蜂的项目结构分成两个部分:业务功能和基础服务。
业务功能部分包含了项目所有的业务相关的功能,也是项目的核心。在这一部分中业务代码集中在 React 这一层,iOS 和 Android 作为接口兼容的统一组件供 JavaScript 调用,省去了开发人员对 Native 层的关心。
React 层
这一层包含了我们全部的业务代码,通过上图可以看到,我们这一层是打的很薄的,主要有 views、components、services、utils 和 mock 这些部分,我们希望页面是通过组装完成的。
- views: views 下面通过功能模块来进行区分,每一个功能模块对应一个文件夹,这样在功能结构上比较容易得到区分。但是经过一段时间的时间后,发现这样带来的后果是当功能的粒度上如何做划分,是一个问题,有的功能模块比较大, 如果都放在一个里面就会造成过于臃肿的问题。
- components: 是和业务完全无关前端组件以及对某些组件的兼容性处理,提供一致的 API 接口给外部。
- services: 和服务的通信,在这里同时也做了服务异常的统一处理。
- utils: 这里放一些工具函数
- mock: 数据 mock 相关,方便和后端进行并行开发。
经过一年的不断迭代,在 React 这一层已经有 50 多个业务模块,接近五万行的业务逻辑代码,因此对于这一层的切割也变的至关重要。现在这一层没有引入任何的 framework,组件之间的通信通过 Event 的方式进行,路由方面主要利用 Navigator 来完成不同 view 之间的跳转和传参。
RN Native 组件层
这一层主要的功能在于通过 RN 提供的接口对 Native 相关的功能进行封装,然后暴露给 React。对于我们来说是相对陌生的一层,因为团队里面没有 Native 相关开发经验的同学,不过感谢大家强大的学习和模仿能力,我们依然在这一层产出了许多的组件。
基础服务层
基础服务是独立于业务的,主要是负责升级和上线相关的事务。
组件的编写
在蜜蜂的项目里面,严格来说分成两种组件,一种是 React 组件,这种组件就和我们平时在 web 中写的 React 组件没有什么区别。另外一种是 RN 组件,这种组件就不是用 JavaScript 完成的了,用 Objective-C 和 Java 来进行实现的。
对于我们来说难点就在于 RN 的组件,因为我们没有 Native 的相关经验,并且组件的编写要严格遵从官方规范,在前期文档不是那么丰富的时候只有通过去看 React Native 的官方组件的源码,看 Facebook 是怎么写的,然后模仿着写。
在对 RN 组件进行实现的过程中,这一年也是经历了不少的波折,记得在 React Native 的某一次升级中,出现了某些组件不能和 JavaScript 通信的问题,编译没有问题,查了半天才发现是 API 的变更造成的,某一个类需要在另外一个命名空间下应用才起作用,而老的依然存在就是没作用。
React Native 的组件也分为两种
- Native Modules: 主要是对原生功能的一些封装,不涉及到对 UI 的操作,例如 Cookie、Toast、设备信息等。
- Native UI Components: 涉及到对 UI 的操作,例如地图展示等。
React Native 组件的编写具体可以看官方文档,作为 web 前端工程师去进行 Native 组件的编写时,还是建议将 iOS 和 Android 的一些基层知识,以及线程和布局方面的知识进行比较深入的学习。
升级策略
蜜蜂对于升级这一块儿的策略也是在不断地升级中,蜜蜂存在着两种升级方式:Code Push 热更新和应用的整体更新。
Code Push 热更新:这也是 React Native 具备的一个特点,当没有 Native 端代码的更新,只有 JavaScript 的更新的时候,可以通过热更新的方式进行,这里的热更新简单来说就是通过对本地的 JS Bundle 进行替换的过程。热更新,我们选用了微软出品的 Code Push,选择的原因很简单:大厂出品,有保障。最开始接入 Code Push 的时候也遇到了一些问题,联系微软的工程师,他们也会很 nice 地回答相关问题,提的 PR 也很快得到了回应,总体感觉不错。
由于 Code Push 最开始的 Server 都是在 Azure 上面的,所以国内更新会有一些缓慢,但是后面更新的速度有所改观,据说是在中国也设置了 Server。
应用整体更新:即重新下载应用,重新安装,当有 Native 代码更新时就必须要通过这种方式进行更新了,但是当应用逐渐趋于稳定后,这种更新的频率就会逐渐降低。
蜜蜂的更新方式有两种,更新策略又分为:Code Push 静默升级、Code Push 提示可取消升级、Code Push 强制升级、Native 提示可取消升级、Native 强制升级
更新策略这么多,那么在代码层次上怎么做呢?我们利用本地版本和远端配置平台上配置的版本进行对比以及 Code Push 本身提供的升级判断,以此来确定每一个版本到底使用哪一种策略进行升级。
Crash 的治理
蜜蜂最开始接入的统计平台是 TalkingData,TalkingData 在统计数据的同时也提供 Crash 的统计,因此我们对于应用的 Crash 监控前期就以 TalkingData 为准。现在回顾蜜蜂的 Crash 的治理,可以分为三个时期:JavaScript 异常治理,iOS 异常治理,React Native bug 和系统厂商 bug 治理。
JavaScript 异常治理:这个事情我们的统计平台还是 TalkingData,在平台上面反馈的 Crash 多为 JavaScript 的异常,这一类的异常多是由于后端数据异常,然后前端没有进行非空的判断或者调用链过长导致的。JavaScript 的异常其实比较好治理的,
只需要将发生 Crash 的 Bundle 的 sourcemap 进行对比一下,看哪一行出了问题,稍加判断后就可以。但是也有一些 JavaScript 异常是由于 React Native 本身的 bug 导致的,记得有一个手势的 bug,就是因为 React 的 bug 导致的,后面给官方提了 issue,进行了升级修改。经过一波 JavaScript 异常的修改,我们在 TalkingData 上的 Crash 率已经变得非常好看了,应用整体的 Crash 率在 0.1% 左右,但殊不知这是一个深深的坑。
iOS 异常治理:前面讲到经过一轮 JavaScript 异常的治理,我们在 TalkingData 上的 Crash 率到达了 0.1% 左右,但是后面美团要求公司内的应用必须接入公司内部的 Crash 监控平台,不接不知道,一接吓一跳,接进去蜜蜂的 iOS Crash 率竟然到了 9%,是的是 9%。后面查了这 9% 的原因,首先在 Crash 率的算法上 TalkingData 和公司平台不一样,TalkingData 的 Crash 率为:crash 数/session 数,公司的为:crash 数/DAU,而恰好蜜蜂又是一个使用高频的应用,有的时候一个用户一天要打开 20 多次。除了算法上的不同,TalkingData 上还有一大波系统的 crash 并没有上报。
纠结完数字后,就开始了漫长了 Crash 治理,不知道有多少个日夜在梦里梦到怎样修复 Crash,也是醉醉的。现在回头来看整个 iOS 异常治理整体的方法还是有迹可循的:
整个 iOS 修复的工程也是持续了一段时间,在这段时间我们不断的对问题进行尝试修复和验证,并将修复过程进行记录,方便以后查阅。
经过一段时间的修复,iOS 的 Crash 率也终于降了下来,在这个过程中少不了 Native 同学的帮助。
React Native bug 和系统厂商 bug: React Native 毕竟还一直在发展中,肯定会有一些 bug。这些 bug 有的可能是 React Native 本身的,也有可能是 React Native 在某些手机上的。但是正如当时选择 React Native 看中的一点一样,Facebook 也在自己的生产环境中使用,
因此对于 React Native 本身的 bug 修复还是比较及时的,官方平均两周一个版本。我们需要做到的就是及时发现问题,然后对问题稍加定位,然后如果能够自己提出解决方案就提 PR(不过一般都有人抢在前面提交了),然后确定自己的升级频率。在对 React Native 版本升级的时候,
需要注意的是一定要回归自己应用的功能,因为有的 React Native 的更改会导致样式等不兼容。
对于前端的机遇与挑战
从 Rect Native 0.17 开始到现在的 0.41,接近一年的时间里面,团队的同学也逐渐习惯了 React Native 的开发方式,在技术栈方面团队也形成了已 React 为基础的开发方式,在人力的调配以及项目间的互换上都提供了一定的便捷性。现在整个应用的业务全部由 web 前端的同学来进行开发,
不过更好的是有一位 iOS 的同学支援,这样在能力上对大家形成了互补,对 web 前端的同学了解 Native 知识提供的帮组。
人力成本上面,现在基本没有太多的 Native 组件需要进行开发了,所以一般业务的开发只需要在 iOS 上进行开发,因为调试什么的都比较方便,然后在后期的时候看下 Android 的兼容性就可以了,整体来说对人力还是有比较大的节约的。一个在 web 上写过 react 的同学转来写 React Native,
成本在于前期的工程配置和开发方式的熟悉,后期的开发基本可以平稳过度。
当然现在应用也有不足的地方,例如在 Android 上面转场不是特别流程,冷启动的时间比较长,但是相信这些通过 Facebook 不断的优化,以及我们自己的优化也应该会有所改观。
总体来说,通过对 React Native 的开发,团队的同学对于移动端的知识有了更多的了解,不再是以前对于容器的那一部分完全封闭的状态。同时也对大家提出了更大的要求,只有对 Native 充分的熟悉,才能进行综合的优化,提高应用性能,提升工作效率。
小结
虽然 React Native 现在还有一些不足,但是对于性能要求不是那么高的应用,还是能够满足的。毕竟我们需要在人力和性能之间,合适地选择一个平衡点。我们 Team 也在关于应用动态化的路上不断探索,期望能够通过更加有效的方式满足企业级应用的要求,欢迎各位有志之士加入我们的团队。
快扫描二维码,与志佳老师来聊聊吧~~