beginWork
对整棵树的每一个节点进行更新操作,通过 switch (workInProgress.tag) 对不同的组件做不同的更新处理,更新当前节点 workInProgress,获取新的 children,并为新的 children 生成他们对应的 Fiber,并最终返回第一个子节点 child;
beginWork首先会判断current是否为 null,当首次渲染时,current必然指向 null;- 如果
current不为 null,则分别从current和workInProgress拿到新旧 props,当新旧 props 不一致,或者 context 发生改变,则将didReceiveUpdate设为 true; - 新旧 props 和 legacyContext 均未改变,则调用
checkScheduledUpdateOrContext检查是否有更新或者 new context(有别于 legacy context)改变。
if (
oldProps !== newProps ||
hasLegacyContextChanged()
) {
didReceiveUpdate = true;
} else {
// 此时 props 和 legacy context 均无变化,检查有没有
// 检查 renderLanes 中是否包含 updateLanes,或者 current.dependencies 不为null
// 且内部的 new context 链表有更新,则返回true
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
...
}
如果 hasScheduledUpdateOrContext 为 false,则走优化路径,执行 attemptEarlyBailoutIfNoScheduledUpdate 函数,此函数会根据 workInProgress.tag 做一些特殊处理,并最终执行 bailoutOnAlreadyFinishedWork 返回 workInProgress.child 或者 null;
if (
!hasScheduledUpdateOrContext &&
// 如果捕获到错误或者暂时挂起
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
// 对特殊fiber节点做一些beginWork阶段必要的处理
function attemptEarlyBailoutIfNoScheduledUpdate(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
switch (workInProgress.tag) {
case HostRoot: ...
case HostComponent: ...
case ClassComponent: ...
case HostPortal: ...
case ContextProvider: ...
case Profiler: ...
case SuspenseComponent: ...
case SuspenseListComponent: ...
case OffscreenComponent: ...
case LegacyHiddenComponent: ...
case CacheComponent: ...
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
总之,beginWork 第一阶段主要是在配置 didReceiveUpdate 全局属性,didReceiveUpdate 为 false 的情况有:
- 新旧 props 不一致,新旧 props 的比较是全等比较,除非上一轮复用
workInProgress.child,否则;didReceiveUpdate为 false 的情况有: hasScheduledUpdateOrContext为 false,没有更新(renderLanes中不包含workInProgress.lanes)且 context 没有更新;current为 null。
beginWork 第二阶段是根据 workInProgress.tag,执行不同的 update 函数生成 workInProgress 的所有子节点,并返回第一个子节点 workInProgress.child,其中不同的 update 函数都会调用 reconcileChildren 方法,这是 React diff 的核心过程,对比和创建新 Fiber 都在这里,我们下一章会详细讲解此方法源码。
completeWork
completeWork 的主要工作是根据 workInProgress.tag 执行不同的逻辑;主要工作是将子节点的一些属性冒泡到父节点,并把子节点的 return 属性指向父节点;这些处理由 bubbleProperties 完成;
bubbleProperties
bubbleProperties 首先会判断 beginWork 阶段生成的 workInProgress.child 是否经过优化得到,即通过 bailoutOnAlreadyFinishedWork 生成。
const didBailout =
// workInProgress.alternate就是current
workInProgress.alternate !== null &&
workInProgress.alternate.child === workInProgress.child;
遍历 workInProgress 所有的 child,将 child.lanes,child.childLanes(child 所有子孙节点的 lanes)合并到 workInProgress.childLanes 中;将 child.subtreeFlags,child.flags 合并到 workInProgress.subtreeFlags;将 child.return 指向 workInProgress;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);
// didBailout即前面所提到的 child 是否经优化得到
// 如果不是
if (!didBailout) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
// 如果是
} else {
subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;
}
child.return = workInProgress;
child = child.sibling;
}
workInProgress.subtreeFlags |= subtreeFlags;
workInProgress.childLanes = newChildLanes;
总之,bubbleProperties 顾名思义,就是将子节点的属性往父节点叠加,即属性的冒泡;
switch (workInProgress.tag)
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
// 直接执行 bubbleProperties 冒泡相关属性
bubbleProperties(workInProgress);
return null;
case ClassComponent: {
const Component = workInProgress.type;
// 判断组件是否为 ContextProvider
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
// 执行 bubbleProperties 冒泡相关属性
bubbleProperties(workInProgress);
return null;
}
case HostRoot: {
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (enableCache) {
popRootCachePool(fiberRoot, renderLanes);
let previousCache: Cache | null = null;
if (workInProgress.alternate !== null) {
previousCache = workInProgress.alternate.memoizedState.cache;
}
const cache: Cache = workInProgress.memoizedState.cache;
if (cache !== previousCache) {
// Run passive effects to retain/release the cache.
workInProgress.flags |= Passive;
}
popCacheProvider(workInProgress, cache);
}
// 从ReactFiberStack中移除相关 contextStack contextFiber rootInstance
popHostContainer(workInProgress);
// 从ReactFiberStack中移除 LegacyContext
popTopLevelLegacyContextObject(workInProgress);
resetMutableSourceWorkInProgressVersions();
// 配置顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会有用
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
workInProgress.flags |= Update;
} else if (!fiberRoot.isDehydrated) {
workInProgress.flags |= Snapshot;
}
}
// 下文介绍 updateHostContainer
updateHostContainer(current, workInProgress);
bubbleProperties(workInProgress);
return null;
}
...
updateHostContainer
updateHostContainer 函数
- 先执行
hadNoMutationsEffects判断子节点是否有变化;
const didBailout = current !== null && current.child === workInProgress.child;
// 如果 workInProgress.child 是经复用得到的,那肯定没有变化
if (didBailout) return true;
// 如果有删除子节点的标记,则肯定算变化
if ((workInProgress.flags & ChildDeletion) !== NoFlags) return false;
// 这里有一个React的TODO,应该有将 hadNoMutationsEffects 判断逻辑移到
// 上一层逻辑 bubbleProperties 后面的打算
// TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
// then we only have to check the `completedWork.subtreeFlags`.
let child = workInProgress.child;
// 遍历所有一级子节点,只要有子节点 flags 中包含 MutationMask 就返回true,即有变化
// 而 child.flags 和 child.subtreeFlags 在 bubbleProperties 中会被并入父节点的
// 同名属性上
while (child !== null) {
if (
(child.flags & MutationMask) !== NoFlags ||
(child.subtreeFlags & MutationMask) !== NoFlags
) {
return false;
}
child = child.sibling;
}
// 默认无变化
return true;
- 子节点有变化,则拿到
fiberRoot的containerInfo属性,即 root 节点,render方法接收的第二个参数; - 执行
appendAllChildrenToContainer函数,将所有子节点加入到一个数组中;将fiberRoot.pendingChildren属性指向此数组; - 执行
finalizeContainerChildren将container.pendingChildren属性指向新的子节点数组。