原理
- 当前的
props和state与之前的作浅比较,如果前后的props和state没有改变,那么组件就不会更新(即组件不会执行这些生命周期函数componentWillUpdate、render、componentDidUpdate)
源码分析
定义
PureComponent并给其原型对象定义了isPureReactComponent属性,用于判断这个组件是不是PureComponent,便于做后续的组件更新等操作。(ReactBaseClassed.js)12345function PureComponent(props, context, updater) {//...}//...pureComponentPrototype.isPureReactComponent = true;检查是否需要更新组件,ReactFiberClassComponent.js
123456789101112131415function checkShouldComponentUpdate() {// ...// 检查该组件是否定义了shouldComponentUpdate方法,如果已经定义,就使用该组件的shouldComponentUpdate方法进行判断if (typeof instance.shouldComponentUpdate === 'function') {// ...return shouldUpdate;}// 使用isPureReactComponent属性进行判断是否是PureComponentif (type.prototype && type.prototype.isPureReactComponent) {return (!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState));}return true;}shallowEqual函数:在上一个源码中我们看到,比较的时候用到了shallowEqual这个函数,这个函数进行的是浅比较。如果两个值是对象,其比较顺序为:比较两个对象引用是否相等 -> 比较两个对象的属性数量是否相等 -> 比较两个对象的第一层属性的值是否相等。(shallowEqual.js)123456789101112131415161718192021222324252627function 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;}
PureComponent使用误区
使用PureComponent后,传入的props的值更新,组件却没有更新
props的值是对象或数组:如果其引用一直没有改变,即使对象的值变了,组件也不会更新12// items对象的引用没有改变,只是更新了其属性值,在判断对象引用是否相等时就会返回true<Item items={this.props.items.filter(item => item.val > 30)}/>
使用PureComponent后,传入的props值未更新,组件却更新了
props的值是对象字面量或数组字面量:虽然对象的值没有改变,但是对象的引用地址发生了改变。导致shallowEqual函数结果一直为true,进而导致PureComponent组件一直更新。123456789// Account是一个PureComponent,以下面方式设置style,每次渲染时style都是新对象,从而导致Account组件在每次渲染的时候都会更新<Account style={{color: 'black'}}/> //对象的引用变了<Account style={this.props.style || {}}/> //默认值{}的引用变了,当this.props.style不存在时,Account组件会一直刷新// 正确的写法如下// 修改:在组件外定义常量const defaultValue = {}// 这样使用时传入的props的引用就不会改变<Account style={this.props.style || defaultValue}/>props的值是函数,且值是匿名函数或者直接使用bind绑定上下文12345678910111213141516171819// 写法1:onChange的值的引用会改变,因为它是匿名函数<MyInput onChange={e => this.props.update(e.target.value)} />// 写法2:bind()函数会创建一个新的函数(称为绑定函数),所以函数的引用会改变<MyInput onChange={this.update.bind(this)} />// 合适的写法1:在constructor中绑定上下文constructor () {this.update = this.update.bind(this)}render() {return < MyInput onChange={this.update} />}// 合适的写法2:使用箭头函数update = () => {}render() {return < MyInput onChange={this.update} />}给
PureComponent设置子组件12345678910// Item是一个PureComponent,如果父组件更新,这个Item始终会被重新渲染<Item><span>text</span></Item>// 原因:上诉代码编译后会变成如下,可以看到React.createElement每次都会返回新的React对象。所以Item总是会被重新渲染<Itemchildren={React.createElement('span', {}, 'text')}/>// 解决方法:将给Item包裹一层父组件,并将父组件设置为PureComponent