zoukankan      html  css  js  c++  java
  • react性能优化

    react以组件的形式来组织逻辑,组件允许我们将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。因此 React 有一些充满了自身特色的性能优化思路,这些思路基本都围绕“组件性能优化”这个中心思想展开:
    这里主要通过一下三个方面进行优化

    • 使用 shouldComponentUpdate 规避重复渲染
    • 使用 PureComponent
    • 使用 React.memo 与 useMemo 缓存组件

    shouldComponentUpdate

    shouldComponentUpdate 是 React 类组件的一个生命周期。
    shouldComponentUpdate里判断props或state的数据是否发生变化,通过返回ture(更新)和false(不更新)来阻止不必要的render.
    shouldComponentUpdate的调用方式入下

    shouldComponentUpdate(nextProps, nextState)
    

    在 React 当中,很多时候我们会不经意间就频繁地调用了 render。为了避免不必要的 render 操作带来的性能开销,React 提供了 shouldComponentUpdate 这个周期,我们可以在这个钩子中进行提前预判,判断是否需要执行后面的周期函数。

    为了更直观的看到这个情况,我们写一个demo来看下具体情况

    这里我们写两个组件一个父组件:
    childA.js

    import React from "react";
    export default class ChildA extends React.Component {
        render () {
            console.log("ChildA 的render方法执行了");
            return (
                <div>
                    子组件A的内容:
                    {this.props.text}
                </div>
            );
        }
    }
    

    这里childB.js和childA相似:展示文本不一样,然后将childA和childB放入父组件app.js中:

    import React from "react";
    import ChildA from './ChildA'
    import ChildB from './ChildB'
    class App extends React.Component {
      state = {
        textA: '我是A的文本',
        textB: '我是B的文本'
      }
      changeA = () => {
        this.setState({
          textA: 'A的文本被修改了'
        })
      }
      changeB = () => {
        this.setState({
          textB: 'B的文本被修改了'
        })
      }
      render() {
        return (
        <div className="App">
          <div className="container">
            <div className='buttons' onClick={this.changeA}>点击修改A处的文本</div>
            <div className='buttons' onClick={this.changeB}>点击修改B处的文本</div>
            <ul>
              <li>
                <ChildA text={this.state.textA}/>
              </li>
            <li>
              <ChildB text={this.state.textB}/>
            </li>
            </ul>
          </div>
        </div>
      );
      }
    }
    export default App;
    
    

    运行代码,我们发现a组将和b组件都进行了render渲染,结果如下:
    屏幕截图 2021-07-30 184344.png

    紧接着,我们点击buttons修改子组件中props的文字,结果如下:
    屏幕截图 2021-07-30 184706.png
    我们会发现,点击第一个按钮,修改a组件props的文字,b组件会重新渲染,修改b组件props的文字,a组件会重新渲染,

    在 React 中,只要父组件发生了更新,那么所有的子组件都会被无条件更新。这就导致了 b组件 的 props 尽管没有发生任何变化,它本身也没有任何需要被更新的点,却还是会走一遍更新流程。

    同样的情况也适用于组件自身的更新:当组件自身调用了 setState 后,那么不管 setState 前后的状态内容是否真正发生了变化,都会重新渲染。

    设置shouldComponentUpdate来进行数据判断

    shouldComponentUpdate (newProps, newStates) {
        // 判断 text 属性在父组件更新前后有没有发生变化,若没有发生变化,则返回 false
        if (this.props.text === newProps.text) {
            return false
        }
        //若不相同,则走一遍流程
        return true
    }
    

    当父组件 App 组件发生更新、进而触发 childB 的更新流程时,shouldComponentUpdate 就相当于一个组件守卫:它会检查新更新的props.text是否和之前的值一致,如果一致,那么就不会执行周期函数,直接返回“false”将整个 childB 的生命周期中断掉即可。只有当 props.text 发生变化时,它才会继续执行生命周期,重新渲染组件。

    设置了 shouldComponentUpdate 后,再次点击更新按钮,修改 childA 的渲染内容时,我们看看渲染结果是怎么样的:
    屏幕截图 2021-08-02 104237.png

    这里我们点击修改A处的按钮,点击后发现childA的组件进行了修改,childB并没有进行重新渲染,避免了不必要的渲染,使用 shouldComponentUpdate 来比较从而进行不必要的更新,避免无意义的渲染,这是 React 组件中最基本的性能优化手段,也是最重要的手段。

    PureComponent

    shouldComponentUpdate 虽然帮我们解决了性能方面的问题,但每次比较,都要手动实现一次 shouldComponentUpdate,说实话有点太麻烦了。

    我们可以将其封装成一个组件,这样可以大大的避免多次去写shouldComponentUpdate

    React有一个 PureComponent 的类,解决了多次写 shouldComponentUpdate 的问题。

    我们使用PureComponent来试试

    export default class ChildA extends React.PureComponent {
        render () {
            console.log("ChildB 的render方法执行了");
            return (
                <div>
                    子组件B的内容:
                    {this.props.text}
                </div>
            );
        }
    }
    
    

    然后点击修改按钮A,预期效果很满意,和shouldComponentUpdate实现效果差不多,

    但是很快就会发现问题,PureComponent只会对值应用发生作用,当对象应用不会发生作用,还会重新渲染

     changeA = () => {
        console.log('xhiiang');
        let { personA } = this.state
        personA.name = 'ZLP'
    
        this.setState({
          personA,
         
        })
      }
    
      export default class ChildA extends React.PureComponent {
        render () {
            console.log("ChildB 的render方法执行了");
            return (
                <div>
                    子组件B的内容:
                    {this.props.text}
                    {this.props.personA.name}
                </div>
            );
        }
    }
    

    PureComponent 与 Component 的就在于:PureComponent 将会在 shouldComponentUpdate 中对组件更新前后的 props 和 state 进行浅比较,并根据浅比较的结果,决定是否需要继续更新流程。

    所以当点击修改数据时会发现,组件并不会重新渲染,因为它们进行浅比较,引用相同

    React.memo和userMemo

    React.memo 是 React 导出的一个顶层函数,它本质上是一个高阶组件,负责对函数组件进行包装。

    import React from "react";
    function childA (props) {
        console.log("ChildA 的render方法执行了");
        return (
            <div>
                子组件A的内容:
                {props.text}
            </div>
        );
    }
    // areEqual 用于对比 props 的变化
    function areEqual (prevProps, nextProps) {
        if (prevProps.text === nextProps.text) {
            return true
        }
        return false
    }
    
    export default React.memo(childA, areEqual)
    

    执行结果如下:
    屏幕截图 2021-08-02 142704.png

    同样的也是,修改组件a,b的数组没有改变,不会重现渲染

    React.memo 接收两个参数,第一个参数是我们需要渲染的组件,第二个参数 用来进行 props 的对比逻辑。之前我们在 shouldComponentUpdate 里面做的事情,现在就可以放在 第二个参数 里来做

    当然你可以不写第二个参数,但是他会根据你组件的props进行浅比较

    useMemo

    React.memo 控制是否需要重渲染一个组件,而 useMemo 控制的则是是否需要重复执行某一段逻辑。

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    

    我们可以把目标逻辑作为第一个参数传入,把逻辑的依赖项数组作为第二个参数传入。这样只有当依赖项数组中的某个依赖发生变化时,useMemo 才会重新执行第一个入参中的目标逻辑。

    import React, { useMemo } from "react";
    export default function ChildB ({ text, count }) {
        console.log("ChildB 组件render 执行");
        // text 文本的渲染逻辑
        const renderText = (text) => {
            console.log('renderText 执行了')
            return <p>
                子组件B的文本内容:
                {text}
            </p>
        }
        
        // count 数字的渲染逻辑
        const renderCount = (count) => {
            console.log('renderCount 执行了')
            return <p>
                子组件B的数字内容:
                {count}
            </p>
        }
    
        // 使用 useMemo 加持两段渲染逻辑
        const textContent = useMemo(() => renderText(text), [text])
        const countContent = useMemo(() => renderCount(count), [count])
        return (
            <div className="childB">
                {textContent}
                {countContent}
            </div>
        );
    }
    
    
    // app.js修改数据
    state = {
        textA: '我是A',
        stateB: {
          text: '我是B的文本',
          count: 10
        }
      }
      changeA = () => {
        this.setState({
          textA:'修改后的A'
         
        })
      }
      changeB = () => {
        this.setState({
          stateB: {
            ...this.state.stateB,
            count: 100
          }
        })
      }
    

    我们使用useMome后,看看执行结果:
    屏幕截图 2021-08-02 154408.png

    首次渲染,textContent和countContent都执行了,

    然后我们点击修改a组件的文字,操作结果如下:
    屏幕截图 2021-08-02 154811.png
    会发现细化的textContent和countContent并没有重新渲染,组件b渲染了

    然后我们在点击修改b的count,执行结果如下:

    屏幕截图 2021-08-02 155142.png

    这次会发现,组件b重新渲染,countContent重新渲染,由于text没有变化,所以textContent也没有重新渲染

    使用userMemo对组件进行更细化的比较。弥补了React.memo 无法感知函数内部状态的遗憾

    码字不易,希望大佬多多指点!

  • 相关阅读:
    第六章 优化服务器设置--高性能MySQL 施瓦茨--读书笔记
    skip-external-locking --mysql配置说明
    mysql配置文件my.cnf详解
    Response.Redirect 打开新窗口的两种方法
    .net中Response.End() 和Response.Redirect("http://dotnet.aspx.cc");
    onclientclick与onclick的问题.
    a href="javascript:void(0)" 是什么意思?加不加上有什么区别?
    ashx是什么文件
    CSS里的 no-repeat
    css中 repeat-x 的简单用法
  • 原文地址:https://www.cnblogs.com/wsjaizlp/p/15093580.html
Copyright © 2011-2022 走看看