react性能优化之shouldComponentUpdate的原理剖析

shouldComponentUpdate原理讲解

  • shouldComponentUpdate是干什么的
  • 怎么使state更新而render函数不执行呢?
  • 使用shouldComponentUpdate完成性能优化
  • 当组件的state没有变化,props也没有变化,render函数可能执行吗?
  • pureComponent的基本用法

shouldComponentUpdate是干什么的

话不多说直接看一个简单的react实例

	import { render } from "react-dom";
	import React from "react";
	import { useState } from "react";
	
	class testPage extends React.Component {
	  constructor(props) {
	    super(props);
	    this.state = {
	      number: 1,
	    };
	  }
	  changeState = () => {
	    this.setState({
	      number: 2,
	    });
	  };
	  render() {
	    console.log("render函数执行了");
	    return (
	      <>
	        <div>这里是number{this.state.number}</div>
	        <button onClick={this.changeState}>点我改变number</button>
	      </>
	    );
	  }
	}

export default testPage;

那么既然你都看到了react的性能优化篇,那么我已经默认你对react基础有一定的了解,react的语法我不过多赘述,这个小demo很简单,页面上显示了number(初始值为1),然后点击按钮 number变成 2。此时来看看render函数打印了几次。

react性能优化之shouldComponentUpdate的原理剖析_第1张图片
毫无意外的,执行两次。

那么此时问题来了

怎么使state更新而render函数不执行呢?

只需要加上这三行代码即可

shouldComponentUpdate(nextProps, nextState) {
    return false;
  }

同时,也许你会疑惑,怎么还有这种需求,数据更新,视图不更新,那我还用react干嘛?
大部分情况下确实是这样,但是此时你考虑一下特殊情况如下。我将按钮点击触发的函数改一改

	 this.setState({
      number: 1,  // 之前是2 我现在改成了1
    });

number初始值是1,我改变之后还是1,那么此时会不会执行render函数呢?
来看效果
react性能优化之shouldComponentUpdate的原理剖析_第2张图片
意料之外又在情理之中,render函数还是执行了。那么这种情况就是我们所说的特殊情况。在一整个项目中肯定会涉及到大量这样state没变化但是render函数执行的情况。那么此时shouldComponentUpdate就排上用场了。

使用shouldComponentUpdate完成性能优化

简单的改写一下shouldComponentUpdate钩子就能完成这个基本的需求啦。

shouldComponentUpdate(nextProps, nextState) {
    if (nextState.number === this.state.number) {
      // 说明state里面的值并没有改
      return false;
    } else {
      return true;
    }
  }

但是我们只考虑到了触发render函数的一种情况哦!props的改变也会触发render函数执行哦!现在思考另外一个问题。

当组件的state没有变化,props也没有变化,render函数可能执行吗?

如果你的答案是“NO”,那么看下面这个例子。

	import { render } from "react-dom";
import React from "react";
import { useState } from "react";

class testPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      numberArray: [1, 2, 3],
    };
  }

  //点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件
  handleClick = (index) => {
    let preNumberArray = this.state.numberArray;
    preNumberArray[index] += 1;
    this.setState({
      numberArray: preNumberArray,
    });
  };

  render() {
    return (
      <div style={{ margin: 30 }}>
        {this.state.numberArray.map((number, key) => {
          return (
            <Son
              key={key}
              index={key}
              number={number}
              handleClick={this.handleClick}
            />
          );
        })}
      </div>
    );
  }
}

class Son extends React.Component {
  render() {
    const { index, number, handleClick } = this.props;
    //在每次渲染子组件时,打印该子组件的数字内容
    console.log(number);
    return <h1 onClick={() => handleClick(index)}>{number}</h1>;
  }
}

export default testPage;

同样的,我也不会对这个函数的语法进行分析,主要功能就是页面展示1,2,3,点击之后数字+1。那么此时你再想想此章节title中的问题的答案。如果组件的props和state没有变化,但是它的父组件render执行了,那么也一并会触发子组件的执行!看实例。
react性能优化之shouldComponentUpdate的原理剖析_第3张图片
开始是1,2,3没错,此时我们点击3之后,视图变成了
react性能优化之shouldComponentUpdate的原理剖析_第4张图片
此时渲染1和2的两个son组件,它们的props是没有变化的,它们的states也是没有变化的,但是它们的render函数还是执行了。
对此,我们依然可以故技重施。 改写shouldComponentUpdate组件。
react性能优化之shouldComponentUpdate的原理剖析_第5张图片
优化完成。那么新的问题又出现了。
思考下面这个例子。

import { render } from "react-dom";
import React from "react";
import { useState } from "react";

class testPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      numberArray: [{ number: 1 }, { number: 2 }, { number: 3 }],
    };
  }

  //点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件
  handleClick = (index) => {
    let preNumberArray = this.state.numberArray;
    preNumberArray[index].number += 1;
    this.setState({
      numberArray: preNumberArray,
    });
  };

  render() {
    return (
      <div style={{ margin: 30 }}>
        {this.state.numberArray.map((item, key) => {
          return (
            <Son
              key={key}
              index={key}
              numberObject={item}
              handleClick={this.handleClick}
            />
          );
        })}
      </div>
    );
  }
}

class Son extends React.Component {

  render() {
    const { numberObject, index, handleClick } = this.props;
    //在每次渲染子组件时,打印该子组件的数字内容
    console.log(numberObject.number);
    return <h1 onClick={() => handleClick(index)}>{numberObject.number}</h1>;
  }
}

export default testPage;

当你认真看完之后也许会发现我在垂死挣扎,就是把数组[1,2,3]换成了对象形式[{number:1},{number:2},{number:3}],然后你自信满满的在son组件中加了如下代码

shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.numberObject.number == this.props.numberObject.number) {
      return false;
    }
    return true;
  }

然后发现
react性能优化之shouldComponentUpdate的原理剖析_第6张图片
不论你怎么点击,页面都不会再有任何反应。所以不论react,vue多么牛逼,它们最终还是用js写的。又回到了js基础中的基础。基本类型和引用对象类型。我们用一张小图来分析它们之间的关系
react性能优化之shouldComponentUpdate的原理剖析_第7张图片
其实在图中可以看出,由于使用的是引用对象而且指向的是同一个内存区域,所以在数据更新的时候,所以在作比较的时候永远是“自己等于自己”。
可以在shouldComponentUpdate钩子中加入一行验证自己的猜想。

	 console.log(nextProps.numberObject === this.props.numberObject);

react性能优化之shouldComponentUpdate的原理剖析_第8张图片

相对应的,解决方案也有很多种。比如利用object.assign,深拷贝或者优秀的第三方js库等等。但是我在此文的最后依然还是要祭出官方提供的杀手锏。如果你仅仅只是为了在state和props不变化的情况下不触发render,可以直接拿出官方的pureComponent

pureComponent的基本用法


class Son extends React.PureComponent {
  render() {
    const { numberObject, index, handleClick } = this.props;
    //在每次渲染子组件时,打印该子组件的数字内容
    return <h1 onClick={() => handleClick(index)}>{numberObject.number}</h1>;
  }
}

你可能感兴趣的:(react,react.js,javascript,前端)