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函数打印了几次。
毫无意外的,执行两次。
那么此时问题来了
怎么使state更新而render函数不执行呢?
只需要加上这三行代码即可
shouldComponentUpdate(nextProps, nextState) {
return false;
}
同时,也许你会疑惑,怎么还有这种需求,数据更新,视图不更新,那我还用react干嘛?
大部分情况下确实是这样,但是此时你考虑一下特殊情况如下。我将按钮点击触发的函数改一改
this.setState({
number: 1, // 之前是2 我现在改成了1
});
number初始值是1,我改变之后还是1,那么此时会不会执行render函数呢?
来看效果
意料之外又在情理之中,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执行了,那么也一并会触发子组件的执行!看实例。
开始是1,2,3没错,此时我们点击3之后,视图变成了
此时渲染1和2的两个son组件,它们的props是没有变化的,它们的states也是没有变化的,但是它们的render函数还是执行了。
对此,我们依然可以故技重施。 改写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 (
<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,vue多么牛逼,它们最终还是用js写的。又回到了js基础中的基础。基本类型和引用对象类型。我们用一张小图来分析它们之间的关系
其实在图中可以看出,由于使用的是引用对象而且指向的是同一个内存区域,所以在数据更新的时候,所以在作比较的时候永远是“自己等于自己”。
可以在shouldComponentUpdate钩子中加入一行验证自己的猜想。
console.log(nextProps.numberObject === this.props.numberObject);
相对应的,解决方案也有很多种。比如利用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>;
}
}