RN沙龙 | 那些携程火车票业务在RN实践中踩过的坑

姚瑞琼,前端程序媛一枚。2014年毕业后加入携程火车票事业部,今年年初起至今,主要负责React Native方案在火车票业务线的实践,先后参与并负责汽车票RN独立版、携程App抢票RN版的开发迭代。

*视频时长约23分钟,请在wifi环境下观看*

火车票作为携程体系下的重要环节,要兼顾良好的App用户体验及迅速的业务迭代,一个月左右一次App版本的节奏很难满足,而React Native跨平台、媲美原生App的用户体验以及无需发版的升级模式等等优势无疑使人眼前一亮。

加上基础团队的Ctrip React Native框架对RN的性能优化、业务封装以及拆包发布等的大力支持,火车票现已上线将近20个RN页面,经历了携程App三个大版本的迭代与考验。

本文将着重介绍React Native在携程火车票产品中的应用,以及在RN实践过程中遇到过的一些实际问题与解决方案。

本文大致分以下几块内容:

1. 为什么选择React Native

2. React Native的现状

3. Ctrip React Native

4. 携程火车票的RN应用

5. 踩过的坑及解决方案

6. 各种问题及优化步骤

一、为什么携程火车票要选择RN

作为目前携程App为数不多的主要以原生开发方式为主的BU,我们也曾在Native跟Hybrid两种方案中纠结过,一方面,原生的交互性能跟用户体验都是最优的,Hybrid则始终还未能突破性能瓶颈;但是另一方面,原生应用的更新又是一个很大的问题,尤其是iOS的App Store,每次发布更新都需要漫长的审核周期,无法做到及时更新,并且上线之后的维护也非常麻烦。

对于业务高速发展、更新比较频繁的火车票业务来说,携程App一个月左右一次的大版本发布已然无法满足需求。所以这个时候,基本兼顾到体验与更新两方面优势的React Native的出现,无疑非常值得我们一试。

从性能体验来说,Native最为优秀,RN基本接近,Hybrid则有些硬伤,因为Hybrid的View层实际上还是前端开发人员所熟知的DOM,而React Native则是以Virtual DOM的方式操作跟渲染相应平台的UI组件,所以性能要高于Hybrid而不逊色于原生;而更新复杂度上,Hybrid跟RN都比较低,可以进行无需发版的bundle包更新,而Native则受限于应用商店的发布更新,复杂度最高;另外,从开发成本角度来看,Native开发周期相对较长,编译调试也相对复杂,并且不能跨平台,Android跟iOS需要维护两套完全不同的代码,RN虽然比Hybrid成本稍高,但是远小于Native,可以做到大部分代码的跨平台复用。

二、React Native 的现状

从这上述的几个方面来看,RN的各项表现都是非常优秀的。然而,目前React Native仍以每两周一个版本的更新频率快速变化中,到现在最新的0.35,仍旧是以零点几的版本在定义,还不能算是一个完全成熟的框架,所以在实际应用过程中还有许多坑要趟。比如版本升级问题,有时候可以平滑过渡,有时候就没那么轻松了。

就拿我们年初实践的汽车票独立版来说,1月份刚开始使用的时候,React Native刚开源Android版本不久,在Android上的兼容性还不是很乐观,所以只在iOS上做了尝试,我们最开始使用的是0.18的RN版本,从如何集成到现有的App里、怎么打全量包或增量包、以及bundle包的发布等等问题,当时都是组里的小伙伴跟iOS开发小伙伴自己一步步摸索过来的,但是在RN的快速更迭下,等尝试升级到零点二几的RN版本时,一言不合某些组件跟API就不能用了。

三、Ctrip React Native

在携程基础团队向我们各个业务团队提出Ctrip React Native的支持时,我们几乎毫不犹豫就确定要在携程火车票里接入了,算是公司里RN应用比较早的BU,也是第一个使用RN进行完整的购票下单流程的BU。

CRN抹平了很多iOS跟Android组件的差距,比如DatePicker、SegmentedControl,提供各种携程风格的组件和API,如HeaderView、HtmlText、Storage、Fetch等,使业务开发更专注于业务逻辑,不需要过多费精力在跨平台的代码实现上,也避免了各个BU的大量重复劳动。

此外,CRN对首屏渲染速度的提升,使iOS能在200ms,Android在400ms左右完成首屏渲染,以及对ListView的优化等都让React Native向Native靠近了一大步;另外,包括对打包拆包、业务代码发布及诸多工具比如crn-cli的提供和支持,都让业务方更无压力的接入RN应用。

可以说,CRN实现了把React Native作为一个纯技术框架像业务框架的转变。

四、携程火车票的React Native应用

携程App从6.17版本开始有业务试用React Native,到6.18也只有2个BU尝试了3个RN页面的上线。到6.19版本,火车票RN强势加盟,7个BU,21个RN页面里,单火车票一家就占了8个。再到6.20版本,在CRN的加持下,15个BU接入了50+的页面,我们到达了17个,包括云抢票、改签抢票、接送站等业务,并且这些页面大部分都比较重要且有较为复杂的交互逻辑。

五、踩过的坑及解决方案

从一个火车票购票流程里粗略提取一下具体实现就有如下几点:

1、从几千个城市站点里选择目标城市

2、在各种车次、座席、出发时间里筛选出合适的车次

3、乘客信息的填写或者选择

首先,购票流程第一步的站点选择,就遇到了一个大坑:大家应该知道全国大大小小的火车站至少有数千个。而接触过RN社区的小伙伴应该知道,React native的GitHub上有条臭名昭著的issue,官方至今还没有完美的解决方案,就是ListView的性能问题。RN自带的ListView是没有回收机制的,这样就使得RN在加载较多个数据的列表,App会非常吃内存。

我们一开始也尝试用自带的ListView来加载城市站点列表,几千条纯Text渲染下来时感觉还能勉强接受,但在加上了View布局、Touchable事件之后,当时连在iPhone 6,iOS 8.2的系统下也非常吃力,越滑越卡,甚至在较低配置的设备上还出现卡死甚至crash的现象。

实际上,城市站点选择是一个变更频率很低但是使用频率很高的页面,考虑到RN ListView的优化空间有限,一旦出现卡死,对火车票来说,结果基本是灾难性的,所以我们最终选择了复用原生已有的城市选择页面,由封装成一个station component供RN使用。

现在我们考虑下另外一个重要场景的实现,从账号里的常用乘客列表里勾选乘车人,同样作为一个列表,是不是也可以像站点列表一样复用native组件呢?我们也确实这样考虑过,看起来好像省时省力、皆大欢喜。但其实正常情况下,个人账号里的乘客数量远小于上千条的站点数据,RN实现是可以负荷下来的。而且station component只需要传给native一个已选的车站,然后native组件里操作完成后返回一个重新选择的车站就可以了。

乘客列表并非简单的单选,而且除了点选,还涉及到新增编辑等等更多的交互。由native封装的话,涉及到的参数传值未免太多。那么是不是可以跳转到一个新的页面,加载跟渲染数量较少的乘客列表比较方便实现呢?

从产品层面来说,火车票购买作为一个购票流程,每多跳转一个页面就有可能损失一部分转化率,所以为了尽可能减少页面的跳转,我们采用了浮层形式在订单填写页面里进行乘车人的选择。然后问题又来了,在浮层弹出的动画过程中加载并渲染乘客列表,很容易出现失帧卡顿的现象。如何解决?

我们是这样考虑的,列表的加载并不是非要在浮层弹出的同时进行的,在进到订单填写页时就可以预先加载好乘客列表数据,而只在浮层里做渲染即可。而且可以在不影响用户视觉体验的前提下,增加一些短时间的延迟。先完成浮层的弹出动画,使用RN InteractionManager的runAfterInteractions等动画结束之后渲染数据,并且设置了ListView的initialListSize跟pageSize均为1来逐条进行渲染。

前面也提到了,我们是希望尽可能减少页面跳转的,所以像车次类型、时间段筛选、座席选择以及前面提到的乘客选择,都是在各个页面采用浮层形式来实现的。但其实浮层涉及到的问题相当多,这里仍旧以乘客选择浮层为例。乘客浮层要实现的功能非常之多,首先,内部的列表是可以滑动的,上部分的阴影可以点击消散浮层,并且内部的Item又要响应各种点击操作。

拿到这么一个复杂的需求,最开始的做法是先给整个Modal加个TouchableHighlight事件来处理消散动作,然后内层加上TouchableWithoutFeedback来避免触发外面的hideModal动作,然后再在每个Item上加各自响应的Touchable事件响应勾选或者取消。

然而,各种Touchable事件嵌套之后,实际效果就不在预期范围内了:滑动内层列表的时候突然划不动,点击Item却没有反应等等,经过一番调试跟定位,终于确定,ScrollView滑动过程中很容易触发到外层的TouchableWithoutFeedback,所以也就withoutfeedback了。

问题一定位,解决方案自然也就出来了,Touchable的过多嵌套导致了问题的产生,那么就应该重新进行层级的布局,避免这些不应该的嵌套,不在整个Modal上加hideModal事件,而是抽出与浮层同级的View来做消散动作,内层只专注于List的渲染跟Item的点击事件监听。

六、各种问题及优化步骤

除了上述那些复杂的需求导致的复杂实现,还遇到过很多比较low的问题。大多数时候如果知道了一些属性就完全可以避免很多问题的产生。

不知道大家有没有遇到过setState方法刚设置完一个状态,取这个状态却发现没有生效的情况。这个异步方法让我写出过很多丑陋的setTimeout来尝试解决。结果查阅React文档后发现setState是有第二个参数的,这个参数就是设置完state之后需要立即调用的函数。

另外,合理使用key属性跟各种React生命周期钩子函数,如shouldComponentUpdate,可以优化很多性能问题。

再比如长按累加累减这样的需求,单纯的onPress跟onLongPress是不能实现的,需要结合delayLongPress直接触发onLongPress,并且在onLongPress里进行setInterval的递增或者递减操作,然后在onPressOut解除press动作的同时clearInterval。

伴随着业务的高速发展,逻辑越来越复杂,我们也逐渐发现了很多做得不好,值得优化的地方。像前面提到过的View层级过多导致的内存消耗、页面卡顿。页面逻辑复杂导致state设置过多,出现切换卡顿现象,并且状态管理越来越混乱。而且现在很容易出现单个页面动不动就一两千,甚至几千行代码,维护起来非常困难,还有很多重复的代码实现等等。

这些问题我们也在考虑从很多方面优化,像布局上尽可能减少层级嵌套,尽可能抽取能够复用的组件,都是大家需要注意的点,状态管理上我们也在考虑如Redux等一些好的解决方案的引入。

以上,希望能与大家共勉。

原文发布于微信公众号 - 携程技术中心(ctriptech)

原文发表时间:2016-11-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏无原型不设计

新手看Mockplus2.3

Mockplus是我从今年下半年开始使用的一款原型设计软件,我被它简单高效的特性深深吸引着。他最近迎来了一次大版本(2.3)的更新,作为一名新手,我看着他再度走...

28970
来自专栏编程微刊

如何获取公众号里面的歌曲

2.3K40
来自专栏钱曙光的专栏

一周极客热文:依赖 IDE 做开发,会让你成为一名糟糕的程序员么?

上图为:Mac系统文本编辑器Chocolat 事实上,一款优秀的IDE会让你的工作更有效率:优质的框架加快了项目进度,自动编译和IDE重构工具让编码进行得更快,...

218100
来自专栏Jerry的SAP技术分享

一个程序猿试用有道云笔记VIP功能体验

熟悉我的朋友应该知道,我有一个微信公众号,叫做“汪子熙", 我会定期在上面推送技术文章。

32100
来自专栏葡萄城控件技术团队

2017年 JavaScript 框架回顾 -- 后端框架

本文是2017年 JavaScript 框架回顾系列的最后的一篇文章,主要介绍 JavaScript 的后端框架情况。 ? 从上图中可以看到,Express 作...

35930
来自专栏腾讯社交用户体验设计

效率至上—全新微云

23230
来自专栏IT派

【前端必看】2017 年 JavaScript 全面崛起大运势

最受欢迎项目 ? 下面是年度最流行的项目,不区分类别。 Vue.js蝉联冠军 Vue.js 再次强势登顶年度排行榜冠军,今年在 GitHub 上新增了超过 40...

35250
来自专栏知晓程序

开发 | 谁说 LBS 小程序开发难?前端女王大人手把手教会你

利用它,你可以在小程序中调用一个功能完整的地图,让小程序里所展示的地点更直观、更精确。

12120
来自专栏大数据钻研

前端工程师如何干掉设计

前端是一个承上启下的职位,正因为其位置的特殊性导致其必须了解设计和后台的一些基本知识。本文并非教大家如何取代设计的工作,而是讲解前端如何更快更便捷的实现一些简单...

35140
来自专栏Pytorch实践

【Python】Selenium辅助海量基金数据获取

今天主要给大家介绍一下Selenium测试工具,它是一款浏览器测试专用的工具,能够模拟用户对浏览器进行鼠标点击、页面滑动等功能。一方面能够用于网页...

41740

扫码关注云+社区

领取腾讯云代金券