React学习:PureComponent的使用

原理

  • 当前的propsstate与之前的作浅比较,如果前后的props和state没有改变,那么组件就不会更新(即组件不会执行这些生命周期函数componentWillUpdaterendercomponentDidUpdate

源码分析

  1. 定义PureComponent并给其原型对象定义了isPureReactComponent属性,用于判断这个组件是不是PureComponent,便于做后续的组件更新等操作。(ReactBaseClassed.js

    1
    2
    3
    4
    5
    function PureComponent(props, context, updater) {
    //...
    }
    //...
    pureComponentPrototype.isPureReactComponent = true;
  2. 检查是否需要更新组件,ReactFiberClassComponent.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function checkShouldComponentUpdate() {
    // ...
    // 检查该组件是否定义了shouldComponentUpdate方法,如果已经定义,就使用该组件的shouldComponentUpdate方法进行判断
    if (typeof instance.shouldComponentUpdate === 'function') {
    // ...
    return shouldUpdate;
    }
    // 使用isPureReactComponent属性进行判断是否是PureComponent
    if (type.prototype && type.prototype.isPureReactComponent) {
    return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
    }
    return true;
    }
  3. shallowEqual函数:在上一个源码中我们看到,比较的时候用到了shallowEqual这个函数,这个函数进行的是浅比较。如果两个值是对象,其比较顺序为:比较两个对象引用是否相等 -> 比较两个对象的属性数量是否相等 -> 比较两个对象的第一层属性的值是否相等。(shallowEqual.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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;
    }

PureComponent使用误区

使用PureComponent后,传入的props的值更新,组件却没有更新

  1. props的值是对象或数组:如果其引用一直没有改变,即使对象的值变了,组件也不会更新
    1
    2
    // items对象的引用没有改变,只是更新了其属性值,在判断对象引用是否相等时就会返回true
    <Item items={this.props.items.filter(item => item.val > 30)}/>

使用PureComponent后,传入的props值未更新,组件却更新了

  1. props的值是对象字面量或数组字面量:虽然对象的值没有改变,但是对象的引用地址发生了改变。导致shallowEqual函数结果一直为true,进而导致PureComponent组件一直更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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}/>
  2. props的值是函数,且值是匿名函数或者直接使用bind绑定上下文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 写法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} />
    }
  3. PureComponent设置子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Item是一个PureComponent,如果父组件更新,这个Item始终会被重新渲染
    <Item>
    <span>text</span>
    </Item>
    // 原因:上诉代码编译后会变成如下,可以看到React.createElement每次都会返回新的React对象。所以Item总是会被重新渲染
    <Item
    children={React.createElement('span', {}, 'text')}
    />
    // 解决方法:将给Item包裹一层父组件,并将父组件设置为PureComponent