Appearance
completeUnitOfWork
这个阶段的主要特点是:
- 产生 stateNode (当前节点对应的 DOM,此时只是保存在内存中)
- 标记
flags和 subtreeFlags (在 Commit 阶段作为判断当前分支是否有 DOM 的操作)
栗子
仍然是 beginWork 的那个栗子
初次渲染
在初次渲染的时候,我们根据 beginWork 按照深度遍历优先的流程知道其当 performUnitOfWork 执行到 第一个没有 child 的节点(this is Comp21 的 Text 节点),因为 next 为 null 进行第一个 completeWork 的流程
代码如下
js
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork = unitOfWork;
do {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
// 当前Node的原FiberNode实例对象
const current = completedWork.alternate;
// 父节点
const returnFiber = completedWork.return;
// Check if the work completed or if something threw.
if ((completedWork.flags & Incomplete) === NoFlags) {
let next;
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
next = completeWork(current, completedWork, subtreeRenderLanes);
}
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next;
return;
}
}
// 如果存在兄弟节点 那么将兄弟节点赋给workInProgress 进入到 兄弟节点的 workLoop 的流程
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// Otherwise, return to the parent
// 如果 不存在兄弟节点 那么通过 do{} 循环执行 父节点的 completeWork流程
// 此时 completedWork指向了父节点,那么在父节点完成 completeWork流程 时也会判断其是否存在兄弟节点,这样形成一个深度优先的链表 遍历流程
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null);
// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}completeUnitOfWork(unitOfWork: Fiber)
不考虑 completeWork 的情况,可以看出来 completeUnitOfWork 的主要作用是 配合 beginWork 完成
- 有兄弟节点的情况下,将 workInProgress 指向兄弟节点
- 没有兄弟节点的情况下,执行父节点的 completeWork
同时根据 (completedWork.flags & Incomplete) === NoFlags 判断当前节点是否存在未完成的副作用,从而决定是执行completeWork还是unwindWork
completeWork(current, completedWork, subtreeRenderLanes)
节点不存在未完成副作用的情况下,对节点进行 DOM 等操作
- 产生 stateNode (当前节点对应的 DOM,此时只是保存在内存中)
- 标记
flags和 subtreeFlags (在 Commit 阶段作为判断当前分支是否有 DOM 的操作)

bubbleProperties()
在 completeWork 中 对于大部分节点都执行了一个 bubbleProperties()方法 , 其作用是通过subtreeFlags替代之前的 finishWork.firstEffect 副作用链表,来标记当前子树中存在副作用,从而避免在 commit 的时候再次深度遍历寻找
- 在 FiberNode 树上通过 FiberNode.subtreeFlags , FiberNode.flags 标记节点和子孙节点 DOM 操作 类型
- FiberNode.flags 标记当前节点 DOM 的操作 更新、创建、删除等
- FiberNode.subtreeFlags 通过位运算标记当前节点树下子孙包含的操作类型
注意: 其跟 FiberNode.lanes 和 FiberNode.childLanes 很像
- 更新 FiberNode.childLanes
- 合并当前节点的 lanes 和 childLanes (completedWork.childLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes)))
在 performUnitOfWork 的时候,Update 的 FiberNode 标记的是 lanes ,祖先节点 标记点 childLanes。 那么这时候通过 lanes 和 childLanes 的合并,从而更好的根据优先级找到更新的节点树分支
js
/**
* 1. 在FiberNode树上通过FiberNode.subtreeFlags , FiberNode.flags 标记节点和子孙节点 DOM操作 类型
* - FiberNode.flags 标记当前节点DOM的操作 更新、创建、删除等
* - FiberNode.subtreeFlags 通过位运算标记当前节点树下子孙包含的操作类型
* 注意: 其跟 FiberNode.lanes 和 FiberNode.childLanes 很像
* 2. 更新 FiberNode.childLanes
* 合并当前节点的 lanes 和 childLanes (completedWork.childLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes)))
* 在performUnitOfWork的时候,Update的FiberNode标记的是lanes ,祖先节点 标记点childLanes。
* 那么这时候通过lanes 和 childLanes的合并,从而更好的根据优先级找到更新的节点树分支
* @param {*} completedWork
* @returns
*/
function bubbleProperties(completedWork: Fiber) {
// 是否能够复用
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;
let newChildLanes = NoLanes;
let subtreeFlags = NoFlags;
if (!didBailout) {
// Bubble up the earliest expiration time.
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
} else {
let child = completedWork.child;
while (child !== null) {
// 合并当前节点的 lanes 和 childLanes
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);
// 跟 FiberNode.lanes 和 FiberNode.childLanes 很像
// 通过位运算标记当前节点树下子孙包含的操作类型
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
}
// 通过位运算标记当前节点树下子孙包含的操作类型
completedWork.subtreeFlags |= subtreeFlags;
}
completedWork.childLanes = newChildLanes;
return didBailout;
}HostComponent
js
case HostComponent: {
popHostContext(workInProgress);
// 获取 Root 的 Container (div#root)
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
// current 和 stateNode 都不为空 说明是更新流程
if (current !== null && workInProgress.stateNode != null) {
// 更新 DOM
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
// 标记是否存在 ref 属性的 更新
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.
// 服务端渲染有关
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// 服务端渲染
} else {
// 生成 DOM 元素
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 将子元素的DOM添加到当前DOM元素下
appendAllChildren(instance, workInProgress, false, false);
// 缓存DOM元素
workInProgress.stateNode = instance;
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
// 处理DOM元素的属性,如果遇到autoFocus 并标记副作用
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
// 标记 flags Update
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
// 标记 flags Ref
markRef(workInProgress);
}
}
return null;
}分为更新流程 和 创建流程(SSR)
更新流程(current !== null && workInProgress.stateNode != null )
updateHostComponent
其核心是一个
prepareUpdate(instance,type ,oldProps, newProps, rootContainerInstance , currentHostContext ),并将需要更新的信息存在 workInProgress.updateQueue中markUpdate(workInProgress); 标记有更新
ref 更新的标识 markRef(workInProgress);
HostText
文本类型的 FiberNode 的 completeWork
js
case HostText: {
const newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
bubbleProperties(workInProgress);
return null;
}副作用标记和副作用链
React17
在旧的任务调度中,如果在 completeWork 阶段发生 DOM 的操作(更新、添加、删除等),都会在 FiberNode.flags 上标记。
同时触发
js
/**
* 在根节点上按照深度优先的规则形成一个
* firstEffect指向第一个有副作用的DOM
* lastEffect指向最后一个有副作用的DOM
* 中间更新的DOM 通过 firstEffect.nextEffect.xxxxxx 形成一个单链
* 因为 CompleteWork是一个由下而上的过程,所以通过
* 将当前节点completedWork.firstEffect保存到父节点的 lastEffect.nextEffect(单链)。
* 然后一层层往上传播,直到 returnFiber !== null 即遇到根节点截止。
* 这样就可以在根节点上收集到所有的产生副作用的节点 FiberNode
*
*/
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.flags & Incomplete) === NoFlags
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
// 如果父节点的 firstEffect 为null 所以暂时没有,所以当前作为第一个
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
// 如果当前节点的lastEffect有值
// 1. 如果父节点的 lastEffect 有值,那么就将当前Node变成 lastEffect.nextEffect 父节点的 lastEffect变成本身
// 2. 如果没有值,那么直接将父节点的lastEffect 变成本身
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if needed,
// by doing multiple passes over the effect list. We don't want to
// schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
const flags = completedWork.flags;
// Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
// 如果当前节点产生DOM的操作
if (flags > PerformedWork) {
// 将当前节点加入到父节点.firstEffect
// 1. 如果当前节点同一层级的前面节点没有副作用,那么这时候 父节点.firstEffect == 父节点.lastEffect == null
// 将当前节点加入到 父节点.firstEffect == 父节点.lastEffect == completedWork
// 2. 如果当前节点同一层级的前面节点也有副作用,那么其不修改父节点..lastEffect.nextEffect = completedWork ; 父节点.lastEffect = completedWork;
// 这样父节点的 fistEffect.nextEffect.nextEffect === lastEffect
//
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
这样在触发点击事件的时候在跟 FiberNode.alternate 上就可以看到一个
React18
在新版的任务调度中 对于副作用的标记主要通过 flags 和 subtreeFlags 去标记副作用节点分支,舍弃了之前的根节点通过副作用firstEffect、lastEffect、nextEffect 形成的单链结构,去找到当前需要更新的 DOM 元素的 FiberNode 节点。
而是使用 flags 和 subtreeFlags 位运算的操作去深度遍历标记
