该文章主要介绍shouldComponentUpdate、pureComponent、memo如何对组件进行优化以及优缺点
1.前提提要
例如在App根组件中我们对其中的state进行数据状态的更改,render函数被调用时,其所有子组件都要重新render并进行diff算法,其性能必然是很低的,执行render的前提是state和props发生改变,然而并不是所有组件都要执行render。如下例子所示
<APP父组件>
import React, { Component } from "react";
import Child from "./Child";
import Child2 from "./Child2";
export class App extends Component {
constructor() {
super();
this.state = {
count: 1,
};
}
addCount() {
this.setState({ count: this.state.count+1 });
}
render() {
console.log("APP被render");
const { count } = this.state;
return (
<div>
<h2>count:{count}</h2>
<Child></Child>
<Child2></Child2>
<button onClick={e => this.addCount()}>count++</button>
</div>
);
}
}
export default App;
<child子组件>
import React, { Component } from "react";
export class child extends Component {
render() {
console.log('child被render');
return <h2>child</h2>;
}
}
export default child;
import React, { Component } from 'react'
export class child2 extends Component {
render() {
console.log('child2被render');
return (
<h2>child2</h2>
)
}
}
export default child2
在APP父组件中,对其内部的count值进行修改,即使child和child2组件没有依赖count值,也会导致子组件执行render,这大大降低了组件的性能。
2.shouldComponentUpdate
shouldComponentUpdate简称SCU,该生命周期有两个参数
- 参数一:nextProps修改之后,最新的props值
- 参数二:nextState修改之后,最新的state值
返回值
- 返回值为true,那么就需要调用render方法
- 返回值为false,就不需要掉render方法
- 默认返回true
export class App extends Component {
constructor() {
super();
this.state = {
count: 1,
};
}
addCount() {
this.setState({ count: 1 });
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count === this.state.count) {
return false;
}
return true;
}
render() {
console.log("APP被render");
const { count } = this.state;
return (
<div>
<h2>count:{count}</h2>
<Child></Child>
<Child2></Child2>
<button onClick={e => this.addCount()}>count++</button>
</div>
);
}
}
如上代码所示,在生命周期shouldComponentUpdate对state值进行是否更改来判断是否进行render,addCount函数并没有对state值进行更改所以不会执行render。但问题是如果组件维护了多个状态就要进行多次判断,甚至如果子组件也依赖其某个状态也要进行多次值是否更改的判断,这会导致代码非常冗余。下面将介绍shouldComponentUpdate的升级版pureComponent。
3.pureComponent
大部分情况下可以用pureComponent代替手写的shouldComponentUpdate,但pureComponent只对state和props进行浅比较,对复杂的数据结构,情况会比较麻烦,下节将会介绍解决办法。
<APP父组件>
export class App extends PureComponent {
constructor() {
super();
this.state = {
count: 1,
val: 1,
};
}
addCount() {
this.setState({ count: this.state.count + 1 });
}
addVal() {
this.setState({ val: this.state.val + 1 });
}
render() {
console.log("APP被render");
const { count, val } = this.state;
return (
<div>
<h2>count:{count}</h2>
<Child count={count}></Child>
<Child2 val={val}></Child2>
<button onClick={e => this.addCount()}>count++</button>
<button onClick={e => this.addVal()}>addVal++</button>
</div>
);
}
}
<Child子组件>
import React, { PureComponent } from "react";
export class child extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { count } = this.props;
console.log("child被render");
return <h2>child:{count}</h2>;
}
}
export default child;
<Child2子组件>
import React, { PureComponent } from "react";
export class child2 extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { val } = this.props;
console.log("child2被render");
return <h2>child2:{val}</h2>;
}
}
export default child2;
Child子组件依赖count状态,child2依赖val状态,当只对val状态进行更改时,只有其依赖count的组件才会进行render。上述例子都是class组件,但PureComponent在函数式组件中无法使用,函数式组件使用memo对依赖的props状态进行比较来判断是否render。
import {memo} from 'react'
const Profile = memo(function(props){
render xxxx
})
export default Profile
4.不可变数据的力量
前节讲到PureComponent只能对浅层的数据比较,但对复杂的数据结构就会有遗漏。如下所示
export class App extends PureComponent {
constructor() {
super();
this.state = {
roles: [
{ name: "早见沙织", country: "Japan" },
{ name: "陶典", country: "China" },
{ name: "菊花花", country: "China" },
{ name: "花玲", country: "China" },
],
};
}
addRole() {
const newRole = { name: "我妻由乃", country: "Japan" };
this.state.roles.push(newRole);
this.setState({ roles: this.state.roles });
}
render() {
console.log("render");
const { roles } = this.state;
return (
<div>
<ul>
{roles.map((item, index) => {
return (
<li key={index}>
<span>
name:{item.name}--country:{item.country}
</span>
</li>
);
})}
<button onClick={e => this.addRole()}>addRole</button>
</ul>
</div>
);
}
}
在上述代码中,this.setState({ roles: this.state.roles }),直接修改原有state,并不会对数据进行更改,避免该问题最简单的方式是避免直接更改你正用于 props 或 state 的引用类型数据值。
addRole() {
const newRole = { name: "我妻由乃", country: "Japan" };
const roles = [...this.state.roles]; // 引用类型的不可变,进行了浅拷贝地址改变
roles.push(newRole);
this.setState({ roles: roles });
}
通过扩展运算符进行浅拷贝改变内存的地址,但其值依然是一样的。
5.总结
以上是个人的粗浅认知,如有问题死不悔改哈哈。