> 文章列表 > react性能优化之shouldComponentUpdate的原理剖析

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

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的原理剖析
毫无意外的,执行两次。

那么此时问题来了

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

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

shouldComponentUpdate(nextProps, nextState) {return false;}

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

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

number初始值是1,我改变之后还是1,那么此时会不会执行render函数呢?
来看效果
react性能优化之shouldComponentUpdate的原理剖析
意料之外又在情理之中,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 (<Sonkey={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的原理剖析
开始是1,2,3没错,此时我们点击3之后,视图变成了
react性能优化之shouldComponentUpdate的原理剖析
此时渲染1和2的两个son组件,它们的props是没有变化的,它们的states也是没有变化的,但是它们的render函数还是执行了。
对此,我们依然可以故技重施。 改写shouldComponentUpdate组件。
react性能优化之shouldComponentUpdate的原理剖析
优化完成。那么新的问题又出现了。
思考下面这个例子。

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

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

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

相对应的,解决方案也有很多种。比如利用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>;}
}