浅谈React组件之性能优化


Posted in Javascript onMarch 02, 2018

高德纳: "我们应该忘记忽略很小的性能优化,可以说97%的情况下,过早的优化是万恶之源,而我们应该关心对性能影响最关键的另外3%的代码。"

不要将性能优化的精力浪费在对整体性能提高不大的代码上,而对性能有关键影响的部分,优化并不嫌早。因为,对性能影响最关键的部分,往往涉及解决方案核心,决定整体的架构,将来要改变的时候牵扯更大。

1. 单个React组件的性能优化

React利用Virtual DOM来提升渲染性能,虽然每一次页面更新都是最组件的从新渲染,但是并不是将之前的渲染内容全部抛弃重来,借助Virtual DOM,React能够计算出对DOM树的最少修改,这就是React默认情况下渲染都很迅速的秘诀;

不过,虽然Virtual DOM能够将每次DOM操作量减少到最小,但,计算和比较Virtual DOM依然是一个复杂的过程;

当然,如果能够在开始计算Virtual DOM之前就判断渲染的结果不会有变化,那么就可以不进行Virtual DOM计算和比较,速度就会更快。

2.shouldComponentUpdate的默认实现方式

既然可以对组件在开始计算Virtual DOM之前判断渲染结果不会有变化时,阻止渲染的进行,从而提升性能,那么我们自然想到使用shouldComponentUpdate(nextProp,nextState)

shouldComponentUpdate函数在render函数之前调用,决定“什么时候不需要从新渲染”;

即返回一个布尔值,决定更新是否进行下去,默认返回true,若返回false则中断更新;

shouldComponentUpdate(nextProp,nextState){
  return (nextProp.completed !== this.props.completed) ||
    (nextProp.text !== this.props.text)
}

其中nextProps为此次更新传入的props,对于这个组件,影响渲染内容的prop只有completed和text,只要确保这两个prop没有变化,shouldComponentUpdate就可以返回false阻止没必要的更新

但是,上述的比较只是‘浅层比较',如果类型是基本类型,只要值相同,那么“浅层比较”

也会认为二者相同:

那,如果prop的类型是复杂的对象怎么办?

对于复杂对象,‘浅层比较'的方式只看这两个prop是不是同一个对象的引用,如果不是,哪怕对象中的内容完全一样也会认为是不同的两个prop。那么使用“深层比较”:但对对象的结构是无法预知的,如果递归对每个字段都进行“深层比较”,不光会让代码更加复杂,也可能会造成性能问题。

所以,要想判断前后的对象类型的prop是相同的,就必须要保证prop是指向同一个JavaScript对象:

<Foo styleProp = {{color: "red"}}>

要避免使用上面的传入方式,应为每次渲染都会重新创建{color: "red"}对象,引用地址每次都不同,将导致每次的styleProp都不同。

const footStyle = {color: "red"};//确保这个初始化只执行一次,不要放在render函数中
<Foo styleProp = {footStyle}>

使用‘单例模式'确保传入的styleProp指向同一个对象

如果是函数呢?

<Foo onToggle={() => onToggleTodo(item.id)}/>

应该避免使用上面的函数传递模式,因为这里赋值的是一个匿名函数,而且是在赋值的时候产生的,也就是说每次渲染都会产生一个新的函数,这就是问题所在。

如果要传递的prop很多呢?

恩~~用React-Redux的话,有对shouldComponentUpdate的默认实现。

3. 对多个React组件的性能优化

当一个React组件被装载、更新和卸载时,组件的一序列生命周期函数会被调用。不过,这些生命周期函数是针对一个特定的React组件函数,在一个应用中,从上而下有很多React组件组合起来,它们之间的渲染过程要更加复杂。

同样一个组件的渲染过程也要考虑三个过程:装载阶段、更新阶段、卸载阶段

对于装载阶段,组件无论如何都要彻底渲染一次,从这个React组件往下的所有子组件,都要经历一遍React组件的装载生命
周期,所以并没有多少优化的事情可做。

对于卸载阶段,只有一个生命周期函数componentWillUnmount,这个函数只是清理componentDidMount添加的事件处理监听等收尾工作,所以,也没有什么可优化的空间;

4. React更新阶段的调和(Reconciliation)过程

在组件更新过程,会构建更新Virtual DOM,并将其与之前的Virtual DOM进行比较,从而找出不同之处,使用最少的DOM操作进行更新

调和过程:即React更新中对Virtual DOM找不同的过程,通常对比两个N个节点的树形结构的算法,时间复杂度是O(n*3),如果直接

使用默认对比,节点过多的话,需要操作的数量太多,而React不可能采用这种算法;

React实际采用的算法时间复杂度是O(N)(时间复杂度只是对一个算法最好和最差情况下需要的指令操作数量级的估量)

React的Reconciliation算法并不复杂,首先检查两个树形的根节点的类型是否相同,根据相同或者不同有不同的处理方式:

节点类型不同的情况

如果树形节点的类型不相同,那就意味着改动很大,直接认为原来的那个树形结构已经没用,可以扔掉,需要从新构建DOM树,原有的树形上的React组件便会经历“卸载”的生命周期;

也就是说,对于Virtual DOM树这是一个“更新”过程,但是却可能引发这个树结构上某些组件的“装载”和“卸载”过程
如:

更新前

<div>
  <Todos />
 </div>

我们想要更新成这样:

<span>
   <Todos />
 </span>

>1. 那么在作比较的时候,一看根节点原来是div,新的是span,类型就不一样了,那么这个算法就废弃之前的div包括里面的所有子节点,从新构建一个span节点和子节点;

>2. 很明显因为根节点不同就将所有的子节点从新构建,这很浪费,但是为了避免O(N*3)的时间复杂度,React这能选择这种比较简单、快捷的方法;

>3. 所以,作为开发者,我们一定要避免上面的浪费的情景出现

节点类型相同的情况

如果两个节点类型相同时,对于DOM元素,React会保留节点对应的DOM元素,只对其节点的属性和内容做对比,然后只修改更新的部分;

节点类型相同时,对于React组件类型,React做得是根据新节点的props去更新节点的组件实例,引发组件的更新过程;

在处理完根节点对比后,React的算法会对根节点的每一个子节点重复一样的操作

多个相同子组件的情况

如果最初组件状态为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />

</ul>

更新后为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />
  <TodoItem text = "Third" />
</ul>

那么React会创建一个新的TodoItem组件实例,而前两个则进行正常的更新过程但是,如果更新后为:

<ul>
  <TodoItem text = "Zero" />
  <TodoItem text = "First" />
  <TodoItem text = "Second" />

</ul>

(这将暴露一个问题)理想处理方式是,创建一个新的TodoItem组件实例放在第一位,后两个进入自然更新过程
但是要让react按照这种方式,就必须找两个子组件的不同之处,而现有计算两个序列差异的算法时间是O(N*2),显然则
不适合对性能要求很高的场景,所以React选择了一个看起来很傻的办法,即挨个比较每个子组件;

React首先认为把text为First的组件的text改为Zero,Second的改为First,最后创建一个text为Second的组件,这样便会破原有的两个组件完成一个更新过程,并创建一个text为Second的新组件

这显然是一个浪费,React也意到,并提供了方克服,不过需要开发人员提供一点帮助,这就是key

Key的使用

key属性可以明确的告诉React每个组件的唯一标识

如果最初组件状态为:

<ul>
  <TodoItem key={1} text = "First" />
  <TodoItem key={2} text = "Second" />

</ul>

更新后为:

<ul>
  <TodoItem key={0} text = "Zero" />
  <TodoItem key={1} text = "First" />
  <TodoItem key={2} text = "Second" />
</ul>

因为有唯一标识key,React可以根据key值,知道现在的第二和第三个组件就是之前的第一和第二个,便用原来的props启动更新过程,这样shouldComponentUpdate就会发生作用,避免无谓的更新;

注意:因为作为组件的唯一标识,所以key必须唯一,且不可变

下面的代码是错误的例子:

<ul>
  todos.map((item,index) => {
      <TodoItem
        key={index}
        text={item.text}
      />
    })
</ul>

使用数组下标作为key值,看起来唯一,但不稳定,因为随着todos数组值的不同,同样一个组件实例在不同的更新过程中数组的下标完全可能不同,把下标当做可以就会让React乱套,记住key不仅要唯一还要确保稳定不可变

需要注意:虽然key是一个prop,但是接受key的组件不能读取key的值,因为key和ref是React保留的两个特殊prop,并没有预期让组将直接访问。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 鼠标滚轮事件
Apr 09 Javascript
javascript调试之DOM断点调试法使用技巧分享
Apr 15 Javascript
js实现文章文字大小字号功能完整实例
Nov 01 Javascript
jQuery中next方法用法实例
Apr 24 Javascript
浅谈JavaScript中运算符的优先级
Jul 07 Javascript
AngularJS实现星星等级评分功能
Sep 24 Javascript
js获取元素的标签名实现方法
Oct 08 Javascript
解决JQuery全选/反选第二次失效的问题
Oct 11 jQuery
微信小程序自定义底部导航带跳转功能
Nov 27 Javascript
js取0-9随机取4个数不重复的数字代码实例
Mar 27 Javascript
vue中input的v-model清空操作
Sep 06 Javascript
原生JavaScript实现购物车
Jan 10 Javascript
vue实现裁切图片同时实现放大、缩小、旋转功能
Mar 02 #Javascript
vue slot 在子组件中显示父组件传递的模板
Mar 02 #Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
Mar 02 #Javascript
vue 使用eventBus实现同级组件的通讯
Mar 02 #Javascript
详解node.js 下载图片的 2 种方式
Mar 02 #Javascript
vue2.0+vue-dplayer实现hls播放的示例
Mar 02 #Javascript
详解vue2.0+vue-video-player实现hls播放全过程
Mar 02 #Javascript
You might like
php中文字母数字验证码实现代码
2008/04/25 PHP
PHP使用ob_start生成html页面的方法
2014/11/07 PHP
Linux系统下使用XHProf和XHGui分析PHP运行性能
2015/12/08 PHP
详解Window7 下开发php扩展
2015/12/31 PHP
PHP中phar包的使用教程
2017/06/14 PHP
PHP7创建销毁session的实例方法
2020/02/03 PHP
showModelessDialog()使用详解
2006/09/21 Javascript
javascript中数组的sort()方法的使用介绍
2013/12/18 Javascript
jQuery ajax请求返回list数据动态生成input标签,并把list数据赋值到input标签
2016/03/29 Javascript
AngularJS中的指令全面解析(必看)
2016/05/20 Javascript
jQuery+PHP实现微信转盘抽奖功能的方法
2016/05/25 Javascript
jQuery实现表格与ckeckbox的全选与单选功能
2016/11/24 Javascript
微信小程序基于本地缓存实现点赞功能的方法
2017/12/18 Javascript
JavaScript基于对象方法实现数组去重及排序操作示例
2018/07/10 Javascript
探秘vue-rx 2.0(推荐)
2018/09/21 Javascript
Vue实现push数组并删除的例子
2019/11/01 Javascript
js实现可爱的气泡特效
2020/09/05 Javascript
js实现简单的随机点名器
2020/09/17 Javascript
解决vue scoped html样式无效的问题
2020/10/24 Javascript
[52:29]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第三局
2016/03/03 DOTA
[01:20]DOTA2上海特级锦标赛现场采访:谁的ID最受青睐
2016/03/25 DOTA
[01:28]国服启动器接入蒸汽平台操作流程视频
2021/03/11 DOTA
python高手之路python处理excel文件(方法汇总)
2016/01/07 Python
微信小程序跳一跳游戏 python脚本跳一跳刷高分技巧
2018/01/04 Python
对Python捕获控制台输出流的方法详解
2019/01/07 Python
Python 寻找局部最高点的实现
2019/12/05 Python
深入浅析Python 函数注解与匿名函数
2020/02/24 Python
Python偏函数Partial function使用方法实例详解
2020/06/17 Python
雅诗兰黛旗下专业男士保养领导品牌:Lab Series
2017/05/15 全球购物
Shopty西班牙:缝纫机在线销售
2018/01/26 全球购物
英国领先的游戏零售商:GAME
2019/09/24 全球购物
美国在线家具网站:GDFStudio
2021/03/13 全球购物
小学教师师德感言
2014/02/10 职场文书
竞选演讲稿范文大全
2014/05/12 职场文书
小学2016年第十八届推普周活动总结
2016/04/05 职场文书
Oracle数据库事务的开启与结束详解
2022/06/25 Oracle