ZHANGYU.dev

October 14, 2023

React中Props的浅对比

React3.8 min to read

上一周去面试的时候,面试官我PureComponent里是如何对比props的,概念已经牢记脑中,脱口而出就是浅对比,接着面试官问我是如何浅对比的,结果我就没回答上来。

趁着周末,再来看看源码里是如何实现的。

类组件的Props对比

类组件是否需要更新需要实现shouldComponentUpdate方法,通常讲的是如果继承的是PureComponent则会有一个默认浅对比的实现。

// ReactBaseClasses.jsfunction 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.jsfunction 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.jsfunction 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.jsexport 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函数最为第二个参数。

内部的处理其实是手动创建了一个$$typeofREACT_MEMO_TYPEReactElement,方便之后的类型判断。

React.memo组件的创建会稍微复杂一些,由于可以传入第二个自定义的compare函数,所以在内部其实会被定义为2种类型的Fiber节点。

但是实际对于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浅对比的分析了~