什么是PureComponent
?
PureComponent
是一个继承了Component
的子类,它会自动加载shouldComponentUpdate
函数。
当组件有更新的时候,shouldComponentUpdate
函数会对组件的state
和props
进行一次浅比较,
如果props
和state
都没有发生变化,就不会执行render
函数,在react性能方面得到了优化。
所以PureComponent
的原理也是通过执行浅比较优化组件性能。
PureComponent
解决了什么问题?
PureComponent
用浅比较的方法解决了当state
或者props
在不变的情况下重复调用render
函数导致性能下降的问题。
PureComponent
和 Component
的区别是什么?
(1)PureComponent
会自动执行shouldComponentUpdate
方法。通过shallowEqual
的浅对比实现组件的性能优化。而Component
要自己调用shouldComponentUpdate
来实现组件的优化。
(2)PureComponent
不仅会影响自身,还会影响子组件。所以使用PureComponent
的最佳情况是展示组件。
a:父组件和子组件都是继承自
Component
,只要有变化时,都会进行更新。b:父组件和子组件都是继承自
PureComponent
,是否更新依赖各自的props、state
。c:父组件继承
PureComponent
,子组件继承Component
,若父组件的state
或props
没有变化,而子组件的有变化。子组件不会触发更新,因为父组件没有更新,子组件也受其影响。d:父组件继承
Component
,子组件继承PureComponent
,那看各自的props、state
。
(3)若是引用类型(数组或对象),需要其引用的地址发生改变才会重新渲染。
(4)若props
和state
每次都会变化,建议还是用Component
,因为PureComponent
的浅比较也要花费时间。
(5)若有shouldComponentUpdate
方法,则会执行它,若没有,判断是否为PureComponent
组件,若是,进行浅比较。
PureComponent
的优缺点:
优点:
不需要开发者使用
shouldComponentUpdate
来进行判断提升性能,它会自己进行浅比较。
缺点
对于引用类型的
props
或者state
,在判断时可能会出问题导致不会触发渲染。
当为基本类型时对比PureComponent
和Component
看个例子:
import React, { Component, PureComponent } from 'react';
class DiffComponent extends Component {
render() {
return (
<div>
比较PureComponent和Component
<div style={{ display: 'flex' }}>
<div style={{ marginRight: 200 }}>
<PureComp />
</div>
<NormalComp />
</div>
</div>
);
}
}
export default DiffComponent;
class PureComp extends PureComponent {
constructor() {
super();
this.state = {
text: 'true', // 值引用
};
console.log('pure-constructor');
}
changeState = () => {
this.setState({
text: 'false',
});
};
render() {
console.log('pure-render',this);
return (
<div>
PureComponent
<button onClick={this.changeState}>Click</button>
<div>{this.state.text}</div>
</div>
);
}
}
class NormalComp extends Component {
constructor() {
super();
this.state = {
text: 'true', // 值引用
};
console.log('normal-constructor');
}
changeState = () => {
this.setState({
text: 'false',
});
};
render() {
console.log('normal-render',this);
return (
<div>
normalComponent
<button onClick={this.changeState}>Click</button>
<div>{this.state.text}</div>
</div>
);
}
}
复制代码
如上所示,PureComp
继承自PureComponent
,NormalComp
继承自Component
,这两个组件都设置了一个state
是text
,
通过点击按钮可以改变text
的值,将text
设置为false
。
对比分析:
(1)组件初始化时:
PureComp
初始化时,依次输入pure-constructor
,pure-render
。
NormalComp
初始化时,依次输入normal-constructor
,normal-render
。
(2)当点击按钮时:
PureCom
组件:
第一次点击按钮,界面的值被改为false
,执行render
方法,输出pure-render
。
后面再多次点击按钮发现,不会再输出pure-render
,说明不会在执行render
方法了。
而NormalComp
组件:
第一次点击按钮,界面的值被改为false
,执行render
方法,输出normal-render
。
后面再多次点击按钮发现,每次点击按钮都会输出normal-render
。说明每次都会执行render
方法。
总结:
这就是PureComponent
内部优化的体现,PureComponent
对state
和props
做了浅比较。
而对于NormalComp
组件而言,继承的Component
,只要当state
发生改变都会执行render
方法。
若想优化判断不要多次执行render
,可以在shouldComponentUpdate
生命周期中进行比较。
返回true
就会重新渲染执行render
,返回false
则不会重新渲染。
同理也适用于string
,number
等基本类型的数据
当为引用类型时对比PureComponent
和Component
还是上面的例子,只是我们在state
中加入arr
,并且在render
方法中渲染出arr
的值。
import React, { Component, PureComponent } from 'react';
class DiffComponent extends Component {
render() {
return (
<div>
比较PureComponent和Component
<div style={{ display: 'flex' }}>
<div style={{ marginRight: 200 }}>
<PureComp />
</div>
<NormalComp />
</div>
</div>
);
}
}
export default DiffComponent;
class PureComp extends PureComponent {
constructor() {
super();
this.state = {
arr: [1], // 类型引用
};
console.log('pure-constructor');
}
changeState = () => {
let {arr} = this.state;
arr.push('2');
this.setState({
arr
});
};
render() {
console.log('pure-render',this.state);
return (
<div>
PureComponent
<button onClick={this.changeState}>Click</button>
{
this.state.arr.map(item=>{
return <div>{item}</div>
})
}
</div>
);
}
}
class NormalComp extends Component {
constructor() {
super();
this.state = {
arr: [1], // 类型引用
};
console.log('normal-constructor');
}
changeState = () => {
let {arr} = this.state;
arr.push('2');
this.setState({
arr
});
};
render() {
console.log('normal-render',this.state);
return (
<div>
normalComponent
<button onClick={this.changeState}>Click</button>
{
this.state.arr.map(item=>{
return <div>{item}</div>
})
}
</div>
);
}
}
复制代码
对比分析:
(1)组件初始化时都依次执行construct
和render
。pureComponent
和Component
没啥区别。
(2)当点击按钮时:
PureComp
组件:
点击按钮时,控制台也未输出任何东西,没有执行render
方法。
NormalComp
组件:
每次点击按钮,控制台都会输出normal-render
。界面的值也会改变,说明每次都会执行render
方法。
总结:
PureComp
组件未重新渲染执行render
,是因为浅对比时发现state
中的arr
引用类型没有发生改变,所以不会执行render
。
若将changeState
修改为如下写法,改变arr
的引用,则PureComponent
会执行render
方法。
changeState = () => {
let newArr = [...this.state.arr];
newArr.push('2');
this.setState({
arr: newArr
});
};
复制代码
PureComponent
源码分析
PureComponent
源码:
export default function PureComponent (props, context) {
Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototye)
PureComponent.prototype.contructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare (nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
复制代码
从上面的源码可以看出,PureComponent
继承了Component
,并且重写了shouldComponentUpdate
的方法。
而shouldComponentUpdate
只是返回了shallowEqual
方法的返回值。
下面来看看shallowEqual
方法的源码
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
复制代码
shallowEqual
函数做的事情如下:
(1)通过is
函数对两个参数进行比较,判断是否相同,相同直接返回true
:基本数据类型值相同,同一个引用对象都表示相同
(2)如果两个参数不相同,判断两个参数是否至少有一个不是引用类型,存在即返回false
,如果两个都是引用类型对象,则继续下面的比较;
(3)判断两个不同引用类型对象是否相同
先通过Object.keys
获取到两个对象的所有属性,具有相同属性,且每个属性值相同即两个对相同(相同也通过is
函数完成)
is
方法实现如下
function is(x, y) {
// SameValue algorithm
if (x === y) {
// 排除 +0 === -0的情况
// x !== 0 || y !== 0 可以确定x,y 都为 0 或者 -0
// 1 / x === 1 / y 可以确定x,y的正负号不同,因为 Infinity !== -Infinity 是成立的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
// x !== x 可以确定 x是不是NaN,y同理,如果x,y都为NaN,返回true
return x !== x && y !== y;
}
}
复制代码
总结
简单总结一下,PureComponent
就是一个继承了Component
的子类并且会自执行shouldComponentUpdate
方法进行shallowEqual
浅比较,以此来解决组件的性能问题。
下次我们再简单实现一个PureComponent
。