React PureComponent 浅比较详解

为什么用 PureComponent ?

PureComponent 是优化 React 应用程序最重要的方法之一,易于实施,只要把继承类从 Component 换成 PureComponent 即可,可以减少不必要的 render 操作的次数,从而提高性能,而且可以少写 shouldComponentUpdate 函数,节省了点代码。

原理

当组件更新时,如果组件的 props 和state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。

具体就是由于PureComponentshouldeComponentUpdate里,实际是对props/state进行了一个浅对比,所以对于嵌套的对象不适用,没办法比较出来。

浅对比??

首先让我们来看下 PureComponent 的具体实现

React里,shouldComponentUpdate 源码为:


if (this._compositeType === CompositeTypes.PureClass) {

  shouldUpdate = !shallowEqual(prevProps, nextProps)

  || !shallowEqual(inst.state, nextState);

}

那我们再来看看 shallowEqual 的源码到底是神马玩意?


const hasOwn = Object.prototype.hasOwnProperty

function is(x, y) {

  if (x === y) {

    return x !== 0 || y !== 0 || 1 / x === 1 / y

  } else {

    return x !== x && y !== y

  }

}

export default function shallowEqual(objA, objB) {

  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

  for (let i = 0; i < keysA.length; i++) {

    if (!hasOwn.call(objB, keysA[i]) ||

        !is(objA[keysA[i]], objB[keysA[i]])) {

      return false

    }

  }

  return true

}

一看上去可能直接蒙圈。接下来我们一步一步来看。

Object.is()

1. Object.is() 这个函数是用来比较两个值是否相等。但这并不同于 === 或者 ==

2. '==' 比较,会把 undefined, null, '', 0 直接转换成布尔型false

    null == undefined // true

3. '===' 它不会进行类型转换,也就是说如果两个值一样,必须符合类型也一样。但是,它还是有两种疏漏的情况。

    +0 === -0 // true,但我们期待它返回false

    NaN === NaN // false,我们期待它返回true

正因为这些原因,Object.is() 应运而生


// 实际上是Object.is()的polyfill

function(x, y) {

    // SameValue algorithm

    if (x === y) {

    // 处理为+0 != -0的情况

      return x !== 0 || 1 / x === 1 / y;

    } else {

    // 处理 NaN === NaN的情况

      return x !== x && y !== y;

    }

};

了解这个我们再来看看刚才的 shallowEqual 代码


// 用原型链的方法

const hasOwn = Object.prototype.hasOwnProperty

// 这个函数实际上是Object.is()的polyfill

function is(x, y) {

  if (x === y) {

    return x !== 0 || y !== 0 || 1 / x === 1 / y

  } else {

    return x !== x && y !== y

  }

}

export default function shallowEqual(objA, objB) {

  // 首先对基本数据类型的比较

  // !! 若是同引用便会返回 true

  if (is(objA, objB)) return true

  // 由于Obejct.is()可以对基本数据类型做一个精确的比较, 所以如果不等

  // 只有一种情况是误判的,那就是object,所以在判断两个对象都不是object

  // 之后,就可以返回false了

  if (typeof objA !== 'object' || objA === null ||

      typeof objB !== 'object' || objB === null) {

    return false

  }

  // 过滤掉基本数据类型之后,就是对对象的比较了

  // 首先拿出key值,对key的长度进行对比

  const keysA = Object.keys(objA)

  const keysB = Object.keys(objB)

  // 长度不等直接返回false

  if (keysA.length !== keysB.length) return false

  // key相等的情况下,在去循环比较

  for (let i = 0; i < keysA.length; i++) {

  // key值相等的时候

  // 借用原型链上真正的 hasOwnProperty 方法,判断ObjB里面是否有A的key的key值

  // 属性的顺序不影响结果也就是{name:'daisy', age:'24'} 跟{age:'24',name:'daisy' }是一样的

  // 最后,对对象的value进行一个基本数据类型的比较,返回结果

    if (!hasOwn.call(objB, keysA[i]) ||

        !is(objA[keysA[i]], objB[keysA[i]])) {

      return false

    }

  }

  return true

}

总结: shallowEqual 会比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是 一个引用,也就是只比较了 第一层 的值,确实很浅,所以深层的嵌套数据是对比不出来的。

案例 (易变数据不能使用一个引用)


import React, { PureComponent } from 'react'

class App extends PureComponent {

      state = {

        items: [1, 2, 3]

      }

      handleClick = () => {

        const { items } = this.state

        items.pop()

        this.setState({ items })

      }

      render () {

        return (<div>

          <ul>

            {this.state.items.map(i => <li key={i}>{i}</li>)}

          </ul>

          <button onClick={this.handleClick}>delete</button>

        </div>)

      }

    }

通过这个案例就会发现无论怎么点 delete按钮 li 的数量都不会改变。就是因为 items 用的是一个引用, shallowEqual 的结果为 true 。改正:


handleClick = () => {

  const { items } = this.state;

  items.pop();

  this.setState({ items: [].concat(items) });

}

在使用 PureComponent 时,应尽量在 render 定义之外创建所有对象、数组和函数,并确保它们在各种调用间,不发生更改。

<Table
    // map每次返回的是一个新数组,所以浅比较会失败
    rows={rows.map(/* ... */)}

    // 枚举对象之间总是不同的
    style={{ color: 'red' }}

    // 箭头函数在作用域中是一个新的未命名的东西,所以它总是不同的
    onUpdate={() => { /* ... */ }}
/>

上述3种写法在 PureComponent 里是不良写法。

 

参考:https://www.jianshu.com/p/eaf64bd66fd4

 

 

 

 

 

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值