起因
用 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。
完结~