概述
React.memo用于缓存组件,可以避免组件不必要的更新。React.memo可以缓存函数组件和class类组件。
源码分析
同React.forwardRef类似,React.memo本质上也是包装下组件,打上$$typeof标记,返回一个对象。
js
function memo(type,compare){
return {
$$typeof:Symbol.for('react.memo')
type,
compare:compare === undefined ? null : compare
}
}
在生成fiber时,被React.memo包装的组件对应生成的fiber.tag是MemoComponent(14)
updateMemoComponent
在beginWork 阶段,React.memo会调用updateMemoComponent来处理子节点fiber。
js
function updateMemoComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
) {
// 首次渲染,挂载时
if (current === null) {
// 获取memo包裹的组件
const type = Component.type;
// 若包裹的组件是一个函数组件并且没有传递比较函数compare
if (isSimpleFunctionComponent(type) && Component.compare === null) {
let resolvedType = type;
// 修改fiber的tag和type
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = resolvedType;
// 调用updateSimpleMemoComponent方法,并返回
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
// 若包裹的组件不是一个函数组件或者compare不为null,则调用createFiberFromTypeAndProps重新生成一个fiber作为子节点
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
workInProgress,
workInProgress.mode,
renderLanes,
);
// 将当前fiber的ref赋值给子节点
child.ref = workInProgress.ref;
// 维护父子fiber指向
child.return = workInProgress;
workInProgress.child = child;
// 返回子fiber
return child;
}
// 若是更新阶段
// 取旧fiber上的子节点
const currentChild = current.child;
// 判断是否需要安排更新:调用checkScheduledUpdateOrcontext检测
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
// 若不需要更新,
if (!hasScheduledUpdateOrContext) {
// 从旧fiber上取出旧属性
const prevProps = currentChild.memoizedProps;
// 判断是否传了检测函数,若没传,则采用shallowEqual进行浅比较
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
// 比较新旧属性是否发生改变以及新旧fiber的ref是否发生了改变
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
// 若满足条件,则调用bailoutOnAlreadyFinishedWork进入bailout优化机制
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// 若需要更新,则调用createWorkInProgress创建子fiber
const newChild = createWorkInProgress(currentChild, nextProps);
// 传递ref
newChild.ref = workInProgress.ref;
// 保证指向
newChild.return = workInProgress;
workInProgress.child = newChild;
// 最后返回子fiber
return newChild;
}
updateSimpleMemoComponent
若React.memo包装的是一个函数组件,且没有自定义compare方法,则React会将当做一个SimpleMemoComponent类型的fiber来处理,调用updateSimpleMemoComponent方法处理子节点,即包裹的函数组件。
js
function updateSimpleMemoComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
) {
// 判断旧fiber是否存在,
if (current !== null) {
// 若存在,则说明是更新操作
// 获取旧fiber上的旧属性
const prevProps = current.memoizedProps;
// 调用shallowEqual方法判断属性是否发生变化,若未变化,且ref没变
if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref
) {
// 则将didReceiveUpdate置为false,表示要短路更新
didReceiveUpdate = false;
// 将旧属性赋值给新属性以及fiber上的属性
workInProgress.pendingProps = nextProps = prevProps;
// 调用checkScheduledUpdateOrContext检测子组件是否有更新以及context是否有变化,若未变化,则进入bailout优化
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 若上述条件不满足,且flags上有强制更新,则将didReceiveUpdate置为true
didReceiveUpdate = true;
}
}
}
// 旧Fiber不存在,说明是挂载操作,首次渲染直接调用updateFunctionComponent方法,并返回其结果
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
辅助方法
shallowEqual
shallowEqual方法用于浅比较两个对象是否相等。is即Object.is,是 JavaScript 中用于判断两个值是否严格相等的方法。
js
function shallowEqual(objA, objB) {
// 首先判断objA与objB是否严格相等,
if (is(objA, objB)) {
return true;
}
// 检测objA,objB的类型
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 获取objA,objB的key值数组
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 若key的长度不相等,则返回false
if (keysA.length !== keysB.length) {
return false;
}
// 比较objA和objB的属性值是否相等
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}