詳解React中Props的淺對比

上一周去面試的時候,面試官我PureComponent裡是如何對比props的,概念已經牢記腦中,脫口而出就是淺對比,接著面試官問我是如何淺對比的,結果我就沒回答上來。

趁著周末,再來看看源碼裡是如何實現的。

類組件的Props對比

類組件是否需要更新需要實現shouldComponentUpdate方法,通常講的是如果繼承的是PureComponent則會有一個默認淺對比的實現。

// ReactBaseClasses.js
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

PureComponent的實現如上,我以前以為在聲明時默認會實現shouldComponentUpdate方法,但實際上並沒有一個默認的方法。

接下來看看shouldComponentUpdate方法的調用。

// ReactFiberClassComponent.js
function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  // 如果實利實現瞭shouldComponentUpdate則返回調用它的結果
  if (typeof instance.shouldComponentUpdate === 'function') {
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    return shouldUpdate;
  }

  // PureReactComponent的時候進行淺對比
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}

可以看出實際上並沒有單獨寫一個shouldComponentUpdate方法給PureReactComponent,而是在對比的時候就返回淺對比的結果。

淺對比的答案都在shallowEqual方法裡瞭。

shallowEqual 淺對比

// shallowEqual.js
function shallowEqual(objA: mixed, objB: mixed): boolean {
  // 一樣的對象返回true
  if (Object.is(objA, objB)) {
    return true;
  }

  // 不是對象或者為null返回false
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  // key數量不同返回false
  if (keysA.length !== keysB.length) {
    return false;
  }

  // 對應key的值不相同返回false
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !Object.is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

shallowEqual方法原理很簡單瞭

  1. 先判斷兩者是否為同一對象。
  2. 判斷兩者的值是否不為object或為null。
  3. 對比兩者key的長度。
  4. 判斷兩者key對應的值是否相同。

原來原理是這樣簡單的對比,如果我面試的時候能夠口噴源碼,會不會工資更高一些呢?

函數組件的淺對比

函數組件的淺對比方式則使用React.memo方法實現。

// ReactMemo.js
export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
  return elementType;
}

React.memo方法同樣支持傳入compare函數最為第二個參數。

內部的處理其實是手動創建瞭一個$$typeof為REACT_MEMO_TYPE的ReactElement,方便之後的類型判斷。

React.memo組件的創建會稍微復雜一些,由於可以傳入第二個自定義的compare函數,所以在內部其實會被定義為2種類型的Fiber節點。

  • 沒有傳入compare函數的為SimpleMemoComponent。
  • 傳入瞭自定義compare函數的為MemoComponent。

但是實際對於Props的比較都是相同的,默認都是調用shallowEqual方法來對比。

updateSimpleMemoComponent

if (
  shallowEqual(prevProps, nextProps) &&
  current.ref === workInProgress.ref
) {
	// ...
}

updateMemoComponent

// ...
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// ... 

至於為什麼要分為2個組件,我也沒大看懂,藍廋香菇,大概是和更新調度相關的。

SimpleMemoComponent的Fiber節點實際等於改瞭個名的函數組件,走流程會直接走到函數組件裡,而MemoComponent則是套瞭一層殼,需要先把殼剝開生成子Fiber節點,再由子Fiber節點的判斷走到函數組件裡。

以上就是Props淺對比的分析瞭~

以上就是詳解React中Props的淺對比的詳細內容,更多關於React中Props的淺對比的資料請關註WalkonNet其它相關文章!