最近接触了个新项目新领域,使用react native来实现直播项目。今天的主题引入就在这个方面切入。
讲句实话,在曾经的几个项目里,我都觉得这辈子都不会遇到rn的性能问题,因为这个性能对项目的掣肘程度,已经被主流设备大幅提升的性能给淹没。以前的项目里决定用rn时,就已经将它的性能问题对项目整体运行的影响考虑在内,感觉还在不降低客户使用感受的范围内的话,才会决定用。
但是最近这个项目,这方面确实让我们吃了不少瘪,尤其实在Android千元靠下这块设备上的表现。而且由于是直播项目,所以主要模块就集成了大量的普通UI交互、动画、流渲染崩和网络,尤其是直播间http和UI刷新,在以前项目里从未考虑到状态机合理分割刷新对整体项目的影响,在这里都碰到了。
这次抛玉引砖,先说说大家都在说rn的性能优化最主要的几块和掺杂些我自己的理解,再说我自己想到的一些点。
- 启动时:
因为rn是基于虚拟Dom的翻译映射机制,将bundle内的JS代码实时的翻译为原生组件。所以在初始化引擎时,基本必定会有一些时间延迟。
初始化RN环境
- 创建Bridge
- Bridge中的JS环境
- RN模块、UI组件
图片来自网络
通过这个初始化的时间,可以直接看到JS引擎和元素的初始化占了一半左右的时间,所以这块性能损耗是必定存在的,这是低一点。
- 渲染时:
因为翻译机制,通信无法使用同步,所以在渲染层面的性能损耗在所难免。再加上react的状态机制维护起来性能高低与代码质量有很大关系,尤其是在单页面状态过多的情况下,状态机刷新后diff算法就变得比较耗性能,所以这点也有不少性能方面的问题。另外在长列表渲染时,真实场景下都是异步渲染,这样就造成了通信中的性能损耗。旧版本的官方组件ListView也曾经出过几次挺严重的bug。虽然rn官网后来推出了FLatList来替代ListView,但实际上长列表组件体验上还是无法跟原生的长列表状态相比,这个问题我们其实是比较难处理的,有一些人封装了Android和iOS原生的TableView和RecycleList但是使用上存在很大问题,而且灵活性较差。
首先先阐述下rn的性能问题的原因
JS端:
加载速度方面。
第一个方式是进行数据缓存。第二个是减少数据的请求,比如用户个人信息和财产存redux(合理且必要的使用redux绝对可以提高项目质量,且不是一点点@高磊)这个可以通过数据复用来解决,比如组件之间通过数据交互减少对数据的请求。第三个是异步加载,这个倒是基本不需要怎么处理就能实现的。
2.滚动方面主要是列表的滚动优化,第一种方式是在通过组件钩子函数在页面渲染前判断是否需要进行更新,避免造成不必要的性能浪费(willUpdate里面去判断)。第二种是组件,官方提供了一些优化方法,比如减少首次渲染的行数,使用户更快的看到数据,确定没帧渲染的行数,超出屏幕外的数据进行处理等等。项目内现在因为还没出现超出500行的渲染,所以暂时未接触到列表的渲染问题。
响应速度方面
主要解决导航跳转响应慢,触摸反馈速度慢的问题。第一种方式是通过动画来解决。怎么解决呢,其实就一个思路,在进行复杂的逻辑处理前,优先播放动画。第二种方式还是渲染前规避掉不必要的渲染,比如进行一个按钮点击更换背景色的,虽然只是更新按钮的背景色,但是依然会受到整个组件的数据影响,这个时候把不必要的渲染去掉会大大提升响应速度。规避的方式还可以通过单独剥离按钮,把颗粒度尽量减小。第三种方式是通过refs直接操作DOM,不走rn的渲染流程。
总结下来如图所示
上面是几个比较大的方面,可能处理起来不是几句代码需要改这样的。下面就为大家介绍一些细节方面的修改。
视情况重写shouldComponentUpdate方法
虽然React里引以为傲的diff算法非常高效,但是执行数量达到一定程度后,也会带来非常大的影响。比如直播间内部,页面会设计反复刷新状态机,那么可使用shouldComponentUpdate来控制component的渲染次数。
如何做?
如果确定该组件渲染完后无需再次更新,即这个组件是一个静态组件,那么可以直接return false。
如果组件比较复杂,自己对RN的更新机制不太熟,可以直接Minxi一下React提供的PureRenderMixin组件
手动实现或使用第三方组件库,比如Immutable-js说白了,就是要确定组件内的不可变数据,让其不再执行diff及render。这个比较彻底,但用不好会走上死路被迫重写。
使用setNativeProps方法
setNativeProps方法可以理解为web的直接修改dom。使用该方法修改View、Text等RN自带的组件,则不会触发组件的componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate等组件生命周期中的方法。
建议频繁更新的操作,如slider、tabs切换等拖曳操作时,使用setNativeProps来更新属性,会获得意想不到的性能体验。
代码片段:
me.refs.tabView.setNativeProps({
style : {
height : 0,
opacity : 0
}
});
最好不要使用阴影效果
React Native 里面的 shadow相关的样式,是非常耗性能的css属性。这在web上,以前android 2.0年代,也是一样耗性能的css属性之一。如果需要使用阴影效果,建议使用图片来代替反而性能更好一些。
最小化DOM
React Native里虚拟dom结构越复杂,则越低效。感觉RN的渲染性能,和以前android2.x时代没多大区别,如果层级结构大于5级,则要考虑下优化了。这没啥技巧,纯靠经验及硬实力。
native端的优化
主要是jsbundle文件装载的时间过长导致的问题,所以这里又两个方向,第一个是减少装载时间,第二个是减小jsbundle的体积。减少装载时间,可以通过缓存jsbundle文件提示速度。也可以提前装载,适用于原生嵌RN的APP使用。减少体积的方式也有两种,第一个是增量更新,第二个是分包,jsbundle里分成两个部分,第一部分是rn自带的base,第二部分才是业务代码biz,分成两块后只需更新biz的代码即可。这部分由于现在项目内还未使用原生嵌入rn(项目内为一个Controller),所以未使用,但是以后肯定会用到,分包按需加载。