一个例子 🌰
Expensive
作为 Input
的children传入
Input
组件中触发state更新
import React, { useState } from 'react'
function Input({ children }) {
const [num, updateNum] = useState(0);
console.log('Input','-----');
return (
<>
<input value={num} onChange={(e) => {
updateNum(+e.target.value)
}} />
<p>num is {num}</p>
{children}
</>
);
}
function Expensive() {
console.log('expensive render');
return <h1>
<span> expensive </span>
</h1>
}
export default function Diff() {
console.log('Diff','-----');
return <Input>
<Expensive />
</Input>
}
为什么fiberRoot在beginWork会命中bailoutOnAlreadyFinishedWork
- 条件1:
oldProps === newProps
prepareFreshStack
中根据root.current
创建 workInProgress
时, pendingProps的值为null
workInProgress = createWorkInProgress(root.current, null);
- current.memoizedProps也为null 所以条件成立
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
- 为什么
current.memoizedProps
为null呢? 因为在 performUnitOfWork
中, 当前fiber beginWork结束之后会将 pendingProps(本次更新的属性) 复制给 memoizedProps(上次更新时用的属性)
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
- 条件2: rootFiber.lanes不存在于 renderLanes中
- 因为产生更新的是
Input
组件
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
为什么Diff会命中bailoutOnAlreadyFinishedWork
- 在
Diff
beginWork中, props没有发生变化
?
- 在 rootFiber命中
bailoutOnAlreadyFinishedWork
后,会cloneChildFibers
,从而workInProgress.pendingProps === current.pendingProps成立
let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
const createFiber = function (
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
this.pendingProps = pendingProps;
- 而在 上次 workInprogress(
本次的current
) beginWork结束之后, 会给 memoizedProps赋值为pendingProps
,从而``workInProgress.pendingProps === current.memoizedProps 成立`
unitOfWork.memoizedProps = unitOfWork.pendingProps;
- 为什么
Diff
组件的fiber上没有 Input组件产生
的更新优先级
?
- Input产生的更新会合并到 祖先的
childLanes
上(即这里的Diff和FiberRoot都会被合并
, 而Diff组件自己的lanes不会改变
,lanes发生改变的只有Input组件对应fiber
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
} else {
if (true) {
if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}
node = parent;
parent = parent.return;
}
- 基于上面两个条件,所以Diff组件命中了
bailoutOnAlreadyFinishedWork
,从而跳过当前fiber的beginWork阶段(所以Diff这个函数只会执行一次
). 但是由于子树中的Input
存在更新(即workInProgress.childlans中存在updateLane
),所以直接cloneChildFibers
并返回子fiber(即这里的Input的fiber节点
)
为什么Expensive只渲染了一次
- 首先在调和
Input
的children时(暂时不考虑Fragment
) , Expensive fiber
的 props没有发生变化(因为 Diff组件命中了 bailoutOnAlreadyFinishedWork)
,并且lanes也不在renderLanes
上,所以会触发 bailoutOnAlreadyFinishedWork
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
- 在
bailoutOnAlreadyFinishedWork
的逻辑中会检查children.childLanes(即children的children是否存在更新)
, 如果不存在更新,则直接跳过子树(这里直接跳过子树
),否则继续调和子树
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
return null;
} else {
cloneChildFibers(current, workInProgress);