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 无法感知函数内部状态的遗憾

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

posted @ 2021-08-03 11:34  认认真真玩代码  阅读(160)  评论(0编辑  收藏  举报
/* 看板娘 */