React组件的性能优化方法

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对象:


登录后复制

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

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

登录后复制

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

如果是函数呢?

 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树这是一个“更新”过程,但是却可能引发这个树结构上某些组件的“装载”和“卸载”过程
如:

更新前

 

   

登录后复制

我们想要更新成这样:

     

登录后复制

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

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

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

节点类型相同的情况

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

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

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

多个相同子组件的情况

如果最初组件状态为:


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制    

更新后为:


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制      

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


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制      

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

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

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

Key的使用

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

如果最初组件状态为:


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制    

更新后为:


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制      

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

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

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


登录后复制登录后复制登录后复制登录后复制登录后复制登录后复制  todos.map((item,index) => {          })

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

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

相关推荐:

关于React组件项目实践

React组件性能优化方法解答

分解React组件的几种进阶方法

以上就是React组件的性能优化方法的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2785156.html

(0)
上一篇 2025年3月8日 17:21:15
下一篇 2025年3月3日 17:11:00

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 3种mysql分表的方法

    一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。 根据个人经验,mysql执行一个sql的过程如下: 1,接收到…

    编程技术 2025年3月8日
    200
  • 监听JS变量的变化方法实例

    我现在有这样一个需求,需要监控js的某个变量的改变, 如果该变量发生变化,则触发一些事件, 不能使用timeinterval之类的定时去监控的方法, 不知道有比较好的解决方案么? 流行的MVVM的JS库/框架都有共同的特点就是数据绑定, 在…

    编程技术 2025年3月8日
    200
  • 六种JS数组去重的方法分享

    方法一: 双层循环,外层循环元素,内层循环时比较值 如果有相同的值则跳过,不相同则push进数组 本文主要和大家分享六种JS数组去重的方法,希望能帮助到大家。 Array.prototype.distinct =function(){ va…

    编程技术 2025年3月8日
    200
  • JS常用的数组方法总结

    这次给大家带来js常用的数组方法总结,在js中使用数据时注意事项有哪些,下面就是实战案例,一起来看一下。 1、concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。 示例:var array1 = [&#…

    编程技术 2025年3月8日
    200
  • 前端常用方法函数实例详解

    本文主要和大家分享前端编写过程常用方法函数实例详解,希望能帮助到大家。 1. push() 方法 定义和用法 push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。 语法 arrayObject.push(newelement…

    编程技术 2025年3月8日
    200
  • react-router4进行代码拆分的方法

    随着前端项目的不断扩大,一个原本简单的网页应用所引用的js文件可能变得越来越庞大。尤其在近期流行的单页面应用中,越来越依赖一些打包工具(例如webpack),通过这些打包工具将需要处理、相互依赖的模块直接打包成一个单独的bundle文件,在…

    2025年3月8日
    200
  • React Native如何使用fetch实现图片上传

    本文介绍了react native使用fetch实现图片上传的示例代码,分享给大家,具体如下:普通网络请求参数是json对象 图片上传的请求参数使用的是formdata对象 使用fetch上传图片代码封装如下: let common_url…

    编程技术 2025年3月8日
    200
  • 常用的数组字符串方法

    这次给大家带来常用的数组字符串方法,使用数组字符串方法的注意事项有哪些,下面就是实战案例,一起来看一下。 一、数组操作 数组去重 利用Object中的key的唯一性,利用key来进行筛选 function unique(arr){    v…

    编程技术 2025年3月8日
    200
  • Vue2 tab切换选项卡的方法

    这次给大家带来vue2 tab切换选项卡的方法,使用vue2 tab切换选项卡的注意事项有哪些,下面就是实战案例,一起来看一下。 最近在学习Vue,看是案例后随便做一个实践,一遍加深理解;这种简单又能实现效果的比较能够接受; html:结构…

    编程技术 2025年3月8日
    200
  • 详解react关于事件绑定this的四种方式_javascript技巧

    在react组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件,而且react还会对这种引用进行缓存,以达到cpu和内存的最大化。在使用了es6 class或者纯函数时,这种自动绑定就不复存在了,我们需要手动实现th…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论