上一周去面试的时候,面试官我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
方法原理很简单了
- 先判断两者是否为同一对象。
- 判断两者的值是否不为
object
或为null。
- 对比两者
key
的长度。 - 判断两者
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
浅对比的分析了~