回到顶部

在react项目中使用shouldComponentUpdate方法进行组件性能优化

时间:1个月前   作者:庞顺龙   浏览:30   [站内原创,转载请注明出处]

标签: Node.js  

在react项目中使用shouldComponentUpdate方法进行组件性能优化,前半部分主要是思路和官方方法解释,最后两张大图为测试对比结果。

react的渲染流程主要是

初始化组件

该阶段会执行组件及其所有子组件的render方法,从而生成第一版的虚拟dom。

组件更新渲染

组件的props或者state任意发生改变就会触发组件的更新渲染。默认情况下其也会执行该组件及其所有子组件的render方法获取新的虚拟dom。

接下来我们聊聊组件更新渲染的性能问题。

react组件更新流程

通过上面分析可以知道组件更新具体过程如下:

执行该组件及其所有子组件的render方法获取更新后的虚拟DOM,即re-render,即使子组件无需更新。
然后对新旧两份虚拟DOM进行diff来进行组件的更新
在这个过程中,可以通过组件的shouldComponentUpdate方法返回值来决定是否需要re-render。

react的整个更新渲染流程可以借用一张图来加以说明:


默认地,组件的shouldComponentUpdate返回true,即React默认会调用所有组件的render方法来生成新的虚拟DOM, 然后跟旧的虚拟DOM比较来决定组件最终是否需要更新。

性能瓶颈分析

借图说话,例如下图是一个组件结构tree,当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):

理想情况下,我们只希望关键路径上的组件进行更新,如下图:


但是,实际效果却是每个组件都完成re-render和virtual-DOM diff过程,虽然组件没有变更,这明显是一种浪费。如下图黄色部分表示浪费的re-render和virtual-DOM diff。


根据上面的分析,react的性能瓶颈主要表现在:
对于props和state没有变化的组件,react也要重新生成虚拟DOM及虚拟DOM的diff。
这个时候,就是shouldComponentUpdate上场的时候了。

shouldComponentUpdate(nextProps, nextState){
   return !isEqual(nextProps, this.props) || !isEqual(nextState, this.state)
}
其中,isEqual方法为判断两个对象是否相等(指的是其对象内容相等,而不是全等)。

通过显示覆盖shouldComponentUpdate方法来判断组件是否需要更新从而避免无用的更新,但是若为每个组件添加该方法会显得繁琐,好在react提供了官方的解决方案,具体做法:

方案对组件的shouldComponentUpdate进行了封装处理,实现对组件的当前属性和状态与上一次的进行浅对比,从而决定组件是否需要更新。

react在发展的不同阶段提供两套官方方案:

PureRenderMin


一种是基于ES5的React.createClass创建的组件,配合该形式下的mixins方式来组合PureRenderMixin提供的shouldComponentUpdate方法。当然用ES6创建的组件也能使用该方案。


import PureRenderMixin from 'react-addons-pure-render-mixin';
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
PureComponent


该方案是在React 15.3.0版本发布的针对ES6而增加的一个组件基类:React.PureComponent。这明显对ES6方式创建的组件更加友好。


import React, { PureComponent } from 'react'
class Example extends PureComponent {
  render() {
    // ...
  }
}
需要指出的是,不管是PureRenderMin还是PureComponent,他们内部的shouldComponentUpdate方法都是浅比较(shallowCompare)props和state对象的,即只比较对象的第一层的属性及其值是不是相同。例如下面state对象变更为如下值:



state = {
  value: { foo: 'bar' }
}
因为state的value被赋予另一个对象,使nextState.value与this.props.value始终不等,导致浅比较通过不了。在实际项目中,这种嵌套的对象结果是很常见的,如果使用PureRenderMin或者PureComponent方式时起不到应有的效果。
虽然可以通过深比较方式来判断,但是深比较类似于深拷贝,递归操作,性能开销比较大。
为此,可以对组件尽可能的拆分,使组件的props和state对象数据达到扁平化,结合着使用PureRenderMin或者PureComponent来判断组件是否更新,可以更好地提升react的性能,不需要开发人员过多关心。

案例分析

TimeLineView 列表页组件面
TimeLineEventView 列表item组件页面
说明:列表页面 数据list.map(TimeLineEventView)

现有一个列表页面,滚动加载下一页,每个项目中均有一个点赞功能,按照逻辑我们一般是在列表jsx页面通过数据list.map(页面item方法)来实现,通过页面state来处理点赞按钮的样式切换:

我们不对列表item进行shouldComponentUpdate处理,下面通过Perf工具来检测当我们点击某一个item赞功能的时候,通过Perf输出页面性能检测结果:


如上图的Print Wasted 模块,我们可以看到当前列表页面,我们点击某一个item的赞功能,页面会产生将近20ms的性能损耗,主要就是item组件 TimeLineEventView 被重新渲染了17次,而这17次都是没有任何变化的item产生的。
然后我们改造TimeLineEventView页面,增加shouldComponentUpdate处理:


shouldComponentUpdate(nextProps, nextState) {
    if (this.props.IsPraised == nextProps.IsPraised) {
      return false;
    }
    return true;
  }
上面的代码就是充写了react默认返回true的shouldComponentUpdate方法,通过逻辑可知,只有被点击的item组件才会返回true,其他均为false,这样就会实现非相关组件不会重新render渲染,改造完毕后,我们再次通过Perf工具进行检测测试,结果如下:


继续看上图的Print Wasted 模块,可以看到,已经没有 TimeLineEventView 相关的组件渲染损耗记录了,说明我们优化成功,目前的渲染损耗在1ms左右,虽然从 20ms 到 1ms并不会对用户产生很大的感知,但是对于滚动加载列表来说,如果数据结构复杂,页面数据量达到几百条,那么损耗的性能可能就是7.8百ms,对于页面来说就会很明显的卡顿等,所以,对组件进行足够、合理的扁平化拆分就显得尤为必要了。

参考:
http://www.cnblogs.com/wonyun/p/6804952.html
http://benchling.engineering/deep-dive-react-perf-debugging/
https://segmentfault.com/a/1190000006741060

React Perf chrome插件地址:
https://chrome.google.com/webstore/detail/react-perf/hacmcodfllhbnekmghgdlplbdnahmhmm


内容均为作者独立观点,不代表八零IT人立场,如涉及侵权,请及时告知。

评论努力加载中...
暂无评论
暂无评论

作者信息

加载中...

手机扫码阅读

热门相关

加载中...
关于我们   联系我们   申请友链   赞助记录   站点地图
© 2014 - 2017 www.80iter.com All Rights Reserved. 京ICP备14042174号-1
Powered by ASP.NET MVC+NancyFx+Bootstrap+SqlServer
本站遵循 CC BY 4.0 协议,转载请注明出处 。