起因

用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;

打印结果如下:
image.png

一开始是没有加两个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,通过打印我们也可以看到:

image.png

那究竟是为什么呢?
最终,我们在一位博客大佬的解释下知道了:

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。

完结~


起风了
120 声望35 粉丝

北冥有鱼,其名为鲲。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。