原创:libo前端
在最近接触了rn以后,也算有了一点了解,今天写的文章就来简单探讨一下关于rn性能的二三事。react
首先在讨论移动端的性能以前,都必需要了解帧的概念,帧率是评判移动端应用性能的一个极为重要的标准。众所周知,不论是手机仍是电脑或是视频,都是由一组静态的图片以一个稳定的速度快速变化所产生的。咱们把这组图片中的每一张图片叫作一帧,而每秒钟显示的帧数直接的影响了用户界面的流畅度和真实感。用iOS举例,iOS设备提供了每秒60的帧率,这就留给了开发者和UI系统大约16.67ms来完成生成一张静态图片(帧)所须要的全部工做。若是在这分派的16.67ms以内没有可以完成这些工做,就会引起丢帧,使界面表现的不够流畅。但一般来讲,保持一个应用在使用过程当中维持每秒60帧的刷新率是一件比较困难的事情,不论是原生代码仍是rn,都没法避免额外的没必要要的性能开销,所以人工的干预是必要的。web
在讨论rn性能优化的方式以前,咱们先讨论一下rn的性能问题会出如今哪里吧。性能优化
首先RN为咱们在移动端提供了JS的运行环境,因此前端开发者们只须要关心如何编写JS代码,画UI只须要画到virtual DOM 中,不须要特别关心具体的平台。markdown
而一样是运行js,RN与h5页面有本质的区别,使得rn可以具有相似原生App的外观和手感,在体验上远远超过h5。RN底层干了不少不少脏活累活,能够把咱们写的JS代码转化成了native代码执行,也就是说RN页面实际上渲染出来的都是原生组件,再也不是webView里的东西了。网络
RN的本质是把中间的这个桥Bridge给搭好,让JS和native能够互相调用。 dom
在原生iOS应用中,和js单线程不一样的一点是,原生应用能够把网络请求或是一些与ui渲染无关的操做放到子线程异步执行,以此增长主线程渲染的流畅性,让主线程尽可能只关注于视图渲染。这是原生应用性能优化的基本概念。异步
那么在rn应用中,rn又提供了一个特殊的线程即js线程,专门用来执行前端代码。因此rn应用中一般会存在以下几种线程:函数
若是只进行js的编写,那么开发过程当中也是接触不到上面两种线程的。但咱们须要了解的是:在rn应用运行过程当中,js线程和主线程是在高度协同下工做的,例如,在用户触发了UI事件响应时,会在主线程接受事件,在传递到js代码,在js线程处理该事件;在js端发起UI更新时,会同时向native端同步数据和UI结构,在主线程完成更新渲染。性能
更新数据到原生支持的视图是批量进行的,而且在事件循环每进行一次的时候被发送到原生端,这一步一般会在一帧时间结束以前处理完(若是一切顺利的话)。若是JavaScript线程有一帧没有及时响应,或者主线程还在忙于上一帧的渲染工做,就被认为发生了一次丢帧。 例如,若是在一个复杂应用的根组件上调用了this.setState,从而致使一次开销很大的子组件树的重绘,那么,这可能会花费一个较长的时间,好比200ms也就是整整12帧的丢失。此时,任何由JavaScript控制的动画都会卡住。只要卡顿超过100ms,用户就会明显的感受到。
这种状况常常发生在Navigator的切换过程当中:当咱们push一个新的路由时,JavaScript须要绘制新场景所需的全部组件,以发送正确的命令给原生端去建立视图。因为切换是由JavaScript线程所控制,所以常常会占用若干帧的时间,引发一些卡顿。有的时候,组件会在componentDidMount函数中作一些额外的事情,这甚至可能会致使页面切换过程当中多达一秒的卡顿。
另外一个例子是触摸事件的响应:若是你在按钮的响应方法里处理一个跨越多个帧的工做,就可能致使按钮自己的点击动画被延迟了(颜色或是透明度的改变)。这是由于JavaScript线程太忙了,不可以处理主线程发送过来的原始触摸事件。结果就不能及时响应这些事件并命令主线程的页面去调整颜色或是透明度了。
##关于性能瓶颈 经过上面的几个例子咱们不难看出一个问题,致使性能受影响的缘由是在重绘上。原生应用的Navigator就从不须要注意卡顿问题。那么是什么致使rn Navigator的切换如此缓慢?
首先,native代码在设备上的运行速度毋容置疑,而JS做为脚本语言,原本就是以快著称,也就是说两边的独立运行都很快,如此看来,性能瓶颈只会出如今两端的通讯上,但两边其实不是直接通讯的,而是经过Bridge作中间人,查找、调用模块、接口等操做逻辑,会产生到能让UI层明显可感知的卡顿,也就是说Bridge在绘制过程当中的转换过程是比较低效的。 这一点咱们能够从一个例子中看出,当咱们上下滚动ScrollView的时候,不论JavaScript线程繁忙到什么地步,甚至于JavaScript线程卡住,ScrollView的滑动流畅度都不会受到影响,由于ScrollView运行和滑动动画是彻底在主线程之上进行的。
然而ScrollView的滚动事件会被分发到JS线程,若是咱们接收每一次滚动事件并在每一次滚动里触发一次ui更新,那应用的使用效果简直是灾难性的。每一次ui更新不只意味着js的重绘,还会触发桥接的代码装换,和主线程接受到正确的指令后的UI视图的生成和渲染。 那么很显然,性能控制就变成了如何尽可能减小Bridge的逻辑。 应用在如下3种状况会须要bridge工做。
这里大体介绍一些比较经常使用的函数和方法。
1.使用shouldComponentUpdate和pureComponent:
react应用中的state和props的改变都会引发re-render,shouldComponentUpdate这个函数是render()函数调用前被调用的,他的两个参数nextProps和nextState,分别表示下一个props和下一个state的值。咱们重写这个钩子,当函数返回false时,阻止接下来的render()调用以及组件从新渲染,反之,返回true时,组件向下走render从新渲染。
例如这样写:
shouldComponentUpdate(nextProps, nextState) {
return nextState.b !== this.state.b
}
复制代码
可使得当先后state或者props值不一致的时候,咱们才会去进行渲染,从而达到了优化的效果。
或是使用pureComponent,自定义的有状态组件能够尽可能继承自pure component,而再也不是component。在阅读了官方介绍后,能够发现说到底它自己只是会自动使用shouldComponentUpdate钩子的普通Component。若是组件继承自pureComponent,就无需再写shouldComponentUpdate的函数了。
2.InteractionManager: Interactionmanager 本质上一个延迟计划函数,它能够自动将一些耗时较长的工做安排到全部互动或动画完成以后再进行。这样能够保证 JavaScript 动画的流畅运行。 应用这样能够安排一个任务在交互和动画完成以后执行:
InteractionManager.runAfterInteractions(() => {
// ...耗时较长的同步执行的任务...
});
复制代码
3.setNativeProps: setNativeProps方法能够理解为web的直接修改dom。使用该方法修改View、Text等RN自带的组件,就不会触发组件的componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate等组件生命周期中的方法。
这样的确会带来必定的性能提高,同时也会使代码逻辑难以理清,并且并无解决从JS侧到Native侧的数据同步开销问题。所以这个方式官方都再也不推荐,更推荐的作法是合理使用setState()和shouldComponentUpdate()方法解决这类问题。
4.LayoutAnimation: 实现动画时通常会采用Animated的接口,但Animated的接口通常会在JavaScript线程中计算出所须要的每个关键帧,而LayoutAnimation则利用了原生的动画库,Core Animation,LayoutAnimation会一次性将想要实现的动画的参数传递给native,再由native完成动画,动画过程js线程就再也不参与了。 可是LayoutAnimation只工做在一次性的动画上,例如若是动画可能会被中途取消,这种状况仍是须要使用Animated。
5.少用状态组件,尽量用无状态组件,无状态组件在转换成为native视图时是不会被实例化的,能够提高性能,但使用的时候也要注意,咱们不能经过ref来获取对象了。