React Native 效能優化元件-PureComponent

Mr.?發表於2018-12-11

背景

React 使用了 PureComponent,在 shouldComponentUpdate 進行淺比較,這裡到底什麼是淺比較呢?

shallowEqual

在React裡,shouldComponentUpdate原始碼為:

if (this._compositeType === CompositeTypes.PureClass) {  
  shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}
複製程式碼

可以看到PureComponent就是對props跟state的前後狀態做了一個淺比較。

看看shallowEqual的原始碼:

const hasOwn = Object.prototype.hasOwnPropertyfunctionis(x, y) {
  if(x === y) {
    returnx !==0 || y !==0 ||1/ x ===1/ y  
} else {
    returnx !== x && y !== y  
  }
}
export default function shallowEqual (objA, objB) {
  if ( is(objA, objB )) 
    return true
  i f( typeofobjA !=='object' || objA === null || typeofobjB !=='object' || objB   ===null) {
    return false
}
const keysA = Object.keys ( objA )
const keysB = Object.keys ( objB )
if  (  keysA.length !== keysB.length)
  return false
for ( leti =0; i < keysA.length; i++) {
    if ( !hasOwn.call ( objB, keysA[i] )  || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }  
  }
return true
}
複製程式碼

Object.is()

在解析 shallowEqual的原始碼之前,先來認識一下 Object.is(),這個函式是用來比較兩個值是否相等。

為什麼要用這個來比較而不是 == 或者 === 呢?

==

首先先看 ==,由於 JS 是弱型別的,如果使用 == 進行比較,== 操作符會自動將 0,‘’(空字串),null,undefined 轉成布林型 false,這樣就會出現

0==' '// true
null==undefined// true
[1] ==true// true
複製程式碼

這顯然是不符合預期的。所以 JS 為我們提供了全等操作符 ===,它不會進行型別轉換,也就是說如果兩個值一樣,必須符合型別也一樣。但是,它還是有兩種疏漏的情況

+0 === -0//true,但我們期待它返回falseNaN===NaN//false,我們期待它返回true
複製程式碼

所以,Object.is() 修復了 `=== 這兩種判斷不符合預期的情況,

function (x, y){
  // SameValue algorithm
  if(x === y) {
    // 處理為+0 != -0的情況
    returnx !==0 || 1/ x ===1/ y;    
  } else {
    // 處理 NaN === NaN的情況
    return x !== x && y !== y;    
  }
};
複製程式碼

這樣就使得 Object.is() 總是返回我們需要的結果。 它在下面6種情況下,會返回 true

  • 兩個值都是 undefined
  • 兩個值都是 null
  • 兩個值都是 true 或者都是 false
  • 兩個值是由相同個數的字元按照相同的順序組成的字串
  • 兩個值指向同一個物件
  • 兩個值都是數字並且
    • 都是正零 +0
    • 都是負零 -0
  • 都是 NaN
  • 都是除零和 NaN 外的其它同一個數字

可以看出Object.is 可以對基本資料型別: null,undefined,number,string,boolean做出非常精確的比較,但是對於引用資料型別是沒辦法直接比較的。

剖析 shallowEquall

// 用原型鏈的方法
const hasOwn = Object.prototype.hasOwnProperty
// 這個函式實際上是 Object.is() 的 polyfill
functionis (x, y) {
  if (x === y) {
    returnx !==0 || y !==0 ||1/ x === 1/ y  
  } else {
    returnx !== x && y !== y  
  }
} 
export default function shallowEqual (objA, objB) {
  // 首先對基本資料型別的比較
  if (is(objA, objB))
    return true
  // 由於 Obejct.is() 可以對基本資料型別做一個精確的比較, 所以如果不等
  // 只有一種情況是誤判的,那就是 object, 所以在判斷兩個物件都不是 object
  // 之後,就可以返回 false 了
  if (typeofobjA !== 'object' || objA === null || typeofobjB !== '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
}
複製程式碼

總結

回到最開始的問題,淺比較為什麼沒辦法對巢狀的物件比較?

由上面的分析可以看到,當對比的型別為Object的時候並且key的長度相等的時候,淺比較也僅僅是用Object.is()對Object的value做了一個基本資料型別的比較,所以如果key裡面是物件的話,有可能出現比較不符合預期的情況,所以淺比較是不適用於巢狀型別的比較的。

相關文章