4、React组件之性能优化

React组件的性能优化

高德纳: "我们应该忘记忽略很小的性能优化,可以说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阻止没必要的更新

    但是,上述的比较只是‘浅层比较’,如果类型是基本类型,只要值相同,那么“浅层比较”
    也会认为二者相同:

    1. 那,如果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)}/>
  • 应该避免使用上面的函数传递模式,因为这里赋值的是一个匿名函数,而且是在赋值的时候产生的,也就是说
    每次渲染都会产生一个新的函数,这就是问题所在。
  1. 如果要传递的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算法并不复杂,首先检查两个树形的根节点的类型是否相同,根据相同或者不同有不同的处理方式:
  1. 节点类型不同的情况

    • 如果树形节点的类型不相同,那就意味着改动很大,直接认为原来的那个树形结构已经没用,可以扔掉,需要从新构建DOM树,
      原有的树形上的React组件便会经历“卸载”的生命周期;
    • 也就是说,对于Virtual DOM树这是一个“更新”过程,但是却可能引发这个树结构上某些组件的“装载”和“卸载”过程
      如:
      更新前
    <div>
       <Todos />
      </div>

    我们想要更新成这样:

    <span>
          <Todos />
      </span>
>1. 那么在作比较的时候,一看根节点原来是div,新的是span,类型就不一样了,那么这个算法就废弃之前的div包括里面的所有子节点,
  从新构建一个span节点和子节点;
  
>2. 很明显因为根节点不同就将所有的子节点从新构建,这很浪费,但是为了避免O(N*3)的时间复杂度,React这能选择这种比较简单、快捷的方法;
  
>3. 所以,作为开发者,我们一定要避免上面的浪费的情景出现
  1. 节点类型相同的情况

    • 如果两个节点类型相同时,对于DOM元素,React会保留节点对应的DOM元素,只对其节点的属性和内容做对比,然后只修改更新的部分;
    • 节点类型相同时,对于React组件类型,React做得是根据新节点的props去更新节点的组件实例,引发组件的更新过程;
    • 在处理完根节点对比后,React的算法会对根节点的每一个子节点重复一样的操作
  2. 多个相同子组件的情况

    • 如果最初组件状态为:
    <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
  3. 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,并没有预期让组将直接访问。

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

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom