起因
用React.PureComponent时,更新state里面的count用的方式是++this.state.count,但是意外的导致组件没有重新render(本人用Hook组件较多,所以感到很疑惑)
import React from 'react';
import { Button } from 'antd-mobile';
class DemoChildClass extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
num: 1
}
}
addNum = () => {
this.setState({
num: ++this.state.num
}, () => {
console.log('fffgggghhhh1111', this.state.num)
})
console.log('fffgggghhhh222', this.state.num)
}
render() {
const { num } = this.state;
return (
<div className='demo-child'>
这是儿子,这个数字就是{num},hhhh< br />
<Button onClick={this.addNum}>子++++</Button>
</div>
)
}
}
export default DemoChildClass;
打印结果如下:
一开始是没有加两个console.log的,所以按照惯识,认为代码没问题,逻辑没问题,但是预期是错误的。
于是,更改一下setstate方式,如下:
addNum = () => {
let { num } = this.state;
this.setState({
num: ++num
})
}
发现他喵的render了?WHF???为什么?
开始时,认为是PureComponent的问题,内部做了优化,导致渲染更新跟预期不一致,于是去看了PureComponent的源码。
PureComponent
PureComponent并不是react与生俱来就有的,而应该是在15.3版本之后才出现的,主要是为了取代之前的PureRenderMixin。
PureComponent其实就是一个继承自Component的子类,会自动加载shouldComponentUpdate函数。当组件需要更新的时候,shouldComponentUpdate会对组件的props和state进行一次浅比较。如果props和state都没有发生变化,那么render方法也就不会触发,当然也就省去了之后的虚拟dom的生成和对比,在react性能方面得到了优化。
PureComponent源码:
export defualut 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)
}
我们可以看到PrueComponent总体来说就是继承了Component,只不过是将shouldComponentUpdate重写成了shallowCompare。而在shallowCompare中只是返回了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中可以看出,其实就是比较了传入的对象是不是一致,也就是浅比较了,props和state是不是一样。从而来实现了一个另类的shouldComponentUpdate函数。所以从源码来,PureCompoennt仅仅是一个props和state的浅比较。当props和state是对象的时候,并不能阻止不必要的渲染。
比较顺序?
step1:先判断两个对象是否地址相同。如果地址相同,就直接返回true;如果地址不相同,就继续判断(基本类型的相等性判断);
step2:再判断两个props的key数量,是否相同,如果相同就继续下一步的判断;如果不相同,就直接返回false;
step3:最后一步,分别判断每个key对应的value值,是否相同。判断value是否相同,使用的是object.is()。
附PureComponent和Component的区别是什么?
1、就像是上面介绍PureComponent一样,和Component的一个最大的区别在于PureComponent会自动执行shouldComponentUpdate函数,通过shallowEqual的浅对比,实现react的性能优化。而Component必须要通过自己去调用生命周期函数shouldComponentUpdate来实现react组件的优化;
2、PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最佳情况是展示组件
(1)加入父组件是继承自PureComponent,而子组件是继承自Component,那么如果当父组件的props或者是state没有变化而子组件的props或者state有变化,那么此时子组件也不会有更新,因为子组件受到父组件的印象,父组件没有更新。
(2)如果,父子组件均继承自PureComponent,那么父子组件的更新就会依赖与各自的props和state。
(3)父组件是继承自Component,而子组件是继承自PureComponent那么就是看各自的props和state。
(4)当然如果父子组件都是继承自Component那么就是只要有更新,那么都会去重新渲染。
3、若是数组和对象等引用类型,则要引用不同,才会渲染;
4、如果prop和state每次都会变,那么PureComponent的效率还不如Component,因为你知道的,进行浅比较也是需要时间;
5、若有shouldComponentUpdate,则执行它,若没有这个方法会判断是不是PureComponent,若是,进行浅比较。
上面一段属于扩展了,我们回到正题,既然PureComponent进行了浅比较,那就说明preState的值和nextState的值是一样的,所以没有进行render,通过打印我们也可以看到:
那究竟是为什么呢?
最终,我们在一位博客大佬的解释下知道了:
this.setState({
num: ++this.state.num
})
就相当于下面这段代码:
const newNum = ++this.state.num;
this.setState({
num: newNum
})
解释一下,【++this.state.num】,去更改了state的值(也更改了preState),但是没有用setState去更新,所以没有render。然后再进行setState,这时PureComponent会进行浅比较,preState和nextState是一样的,所以还是没有进行render。
也就是说在PureComponent组件中,假如先进行数据更新,然后再进行setState的话,都会是一样的结果,不会render。然鹅,在普通Component组件中,这些问题将不再存在。
所以,在PureComponent组件中,更新state最好的方式是更改state的引用地址,来触发render,但是如果都是采用这种方式的话,就不要用PureComponent了,直接用Component吧!!
最后再来解释一下下面这个写法为啥没问题:
addNum = () => {
let { num } = this.state;
this.setState({
num: ++num
})
}
解构或者重新定义一个值去接受num,相当于在栈内存中重新开辟了一块内存空间变量叫num2,然后进行++num2,改的只是num2,state中num还是原来的值,所以在进行浅比较时,preState和nextState是不一样的,所以会重新render。
完结~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。