构建其余节点
上面已经看完beginWork
,是从父到子,构建子级节点的 fiber 对象,这是递归的 “递”阶段。
接着解析 completeUnitOfWork
,这是从子到父,构建其余节点的 fiber 对象,是递归的 “归”阶段。
completeUnitOfWork
不仅仅是构建其余节点的 fiber 对象,它在从子级到父级的过程中会经过每一个 fiber 节点对象,主要完成的事情包括:
- 构建其余节点的 fiber 对象
- 为每个 fiber 节点对象构建对应的真实 DOM 对象,并添加到
stateNode
属性中 - 收集要执行 DOM 操作的 fiber 节点,组建 effect 链表结构
- 过程中,不断收集当前 fiber 对象要执行 DOM 操作的子 fiber
- 最后将所有要执行 DOM 操作的 fiber 节点对象都挂载到顶层 rootFiber 对象中
completeUnitOfWork
// packages\react-reconciler\src\ReactFiberWorkLoop.js
/**
* 1. 创建 Fiber 对象
* 2. 创建每一个节点的真实 DOM 对象并将其添加到 stateNode 属性中
* 3. 收集要执行 DOM 操作的 Fiber 节点, 组建 effect 链表结构
*/
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
// 为 workInProgress 全局变量重新赋值
workInProgress = unitOfWork;
do {
// 获取备份节点
// 初始化渲染 非根 Fiber 对象没有备份节点 所以 current 为 null
const current = workInProgress.alternate;
// 父级 Fiber 对象, 非根 Fiber 对象都有父级
const returnFiber = workInProgress.return;
// 判断传入的 Fiber 对象是否构建完成, 任务调度相关
// & 是表示位的与运算, 把左右两边的数字转化为二进制
// 然后每一位分别进行比较, 如果相等就为1, 不相等即为0
// 此处应用"位与"运算符的目的是"清零"
// true
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// 开发环境代码 忽略
setCurrentDebugFiberInDEV(workInProgress);
let next;
// 如果不能使用分析器的 timer, 直接执行 completeWork
// enableProfilerTimer => true
// 但此处无论条件是否成立都会执行 completeWork
if (
!enableProfilerTimer ||
(workInProgress.mode & ProfileMode) === NoMode
) {
// 重点代码(二)
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);
} else {
// 否则执行分析器timer, 并执行 completeWork
startProfilerTimer(workInProgress);
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
stopWorkTimer(workInProgress);
resetCurrentDebugFiberInDEV();
resetChildExpirationTime(workInProgress);
// 重点代码(一)
// 如果子级存在
if (next !== null) {
// 返回子级 一直返回到 workLoopSync
// 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
return next;
}
// 构建 effect 链表结构
// 如果不是根 Fiber 就是 true 否则就是 false
// 将子树和此 Fiber 的所有 effect 附加到父级的 effect 列表中
if (
// 如果父 Fiber 存在 并且
returnFiber !== null &&
// 父 Fiber 对象中的 effectTag 为 0
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上
// 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
if (returnFiber.firstEffect === null) {
// first
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
// next
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
// last
returnFiber.lastEffect = workInProgress.lastEffect;
}
// 获取副作用标记
// 初始渲染时除[根组件]以外的 Fiber, effectTag 值都为 0, 即不需要执行任何真实DOM操作
// 根组件的 effectTag 值为 3, 即需要将此节点对应的真实DOM对象添加到页面中
const effectTag = workInProgress.effectTag;
// 创建 effect 列表时跳过 NoWork(0) 和 PerformedWork(1) 标记
// PerformedWork 由 React DevTools 读取, 不提交
// 初始渲染时 只有遍历到了根组件 判断条件才能成立, 将 effect 链表添加到 rootFiber
// 初始渲染 FiberRoot 对象中的 firstEffect 和 lastEffect 都是 App 组件
// 因为当所有节点在内存中构建完成后, 只需要一次将所有 DOM 添加到页面中
if (effectTag > PerformedWork) {
// false
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
// 为 fiberRoot 添加 firstEffect
returnFiber.firstEffect = workInProgress;
}
// 为 fiberRoot 添加 lastEffect
returnFiber.lastEffect = workInProgress;
}
}
} else {
// 初始渲染不执行
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(workInProgress, renderExpirationTime);
// Because this fiber did not complete, don't reset its expiration time.
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
// Include the time spent working on failed children before continuing.
let actualDuration = workInProgress.actualDuration;
let child = workInProgress.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
workInProgress.actualDuration = actualDuration;
}
if (next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// TODO: The name stopFailedWorkTimer is misleading because Suspense
// also captures and restarts.
stopFailedWorkTimer(workInProgress);
next.effectTag &= HostEffectMask;
return next;
}
stopWorkTimer(workInProgress);
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}
// 获取下一个同级 Fiber 对象
const siblingFiber = workInProgress.sibling;
// 如果下一个同级 Fiber 对象存在
if (siblingFiber !== null) {
// 返回下一个同级 Fiber 对象
return siblingFiber;
}
// 否则退回父级
workInProgress = returnFiber;
} while (workInProgress !== null);
// 当执行到这里的时候, 说明遍历到了 root 节点, 已完成遍历
// 更新 workInProgressRootExitStatus 的状态为 已完成
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
return null;
}
从子到父的流程
该方法整体就是一个 do...while
循环。
- 在循环中首先判断是否有子级
- 如果有子级,就返回子级所对应的 fiber 对象,去构建子级的子级
- 如果没有子级,就会判断是否有同级
- 如果有同级,就返回同级,去构建同级的子级
- 如果没有同级,就会返回父级,判断父级是否有同级
- 如果父级有同级,就返回父级的同级,去构建它的子级
- 如果没有同级,返回父级的父级,依次向上直到 rootFiber
相关代码:
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
// 为 workInProgress 全局变量重新赋值
workInProgress = unitOfWork;
do {
/* if_else start*/
// 如果子级存在
if (next !== null) {
// 返回子级 一直返回到 workLoopSync
// 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
return next;
}
/* if_else end*/
// 获取下一个同级 Fiber 对象
const siblingFiber = workInProgress.sibling;
// 如果下一个同级 Fiber 对象存在
if (siblingFiber !== null) {
// 返回下一个同级 Fiber 对象
return siblingFiber;
}
// 否则退回父级
workInProgress = returnFiber;
} while (workInProgress !== null);
}
创建节点真实 DOM 对象并将其添加到 stateNode 属性中
通过调用 completeWork
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);
completeWork
completeWork
内部就是一个 switch
,匹配当前节点的类型 tag
。
因为并不是所有的 fiber 节点,都能创建对应的 DOM 对象,例如最先判断的一些类型,直接返回 null
。
- ClassComponent 类组件
- HostRoot root 节点
- HostComponent 普通的 React 元素
- 等
这里只解析下普通 React 元素的处理:
- 最终会调用
createInstance
方法,创建当前节点的实例对象,即真实的 DOM 对象。 - 然后调用
appendAllChildren
构建当前节点所有子级的真实 DOM 对象,追加到当前节点中 - 当构建完 DOM 对象后,将当前节点的 DOM 对象添加到 fiber 对象的
stateNode
属性中
// packages\react-reconciler\src\ReactFiberCompleteWork.js
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
// 获取待更新 props
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
// 0
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent:/*...*/
// 3
case HostRoot:/*...*/
// 5
case HostComponent: {
popHostContext(workInProgress);
// 获取 rootDOM 节点 <div id="root"></div>
const rootContainerInstance = getRootHostContainer();
// 节点的具体的类型 div span ...
const type = workInProgress.type;
// 初始渲染不执行 current = null
if (current !== null && workInProgress.stateNode != null) {
/*...*/
} else {
/*...*/
// 服务器渲染相关 初始渲染为不执行
// false
if (wasHydrated) {
/*...*/
} else {
// 创建节点实例对象 <div></div> <span></span>
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
/**
* 将所有的子级追加到父级中
* instance 为父级
* workInProgress.child 为子级
*/
appendAllChildren(instance, workInProgress, false, false);
// 为 Fiber 对象添加 stateNode 属性
workInProgress.stateNode = instance;
// 初始渲染不执行
// false
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
/*...*/
}
// 处理 ref DOM 引用
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
// 6
case HostText:/*...*/
case SuspenseComponent:/*...*/
case HostPortal:/*...*/
case ContextProvider:/*...*/
case IncompleteClassComponent:/*...*/
case SuspenseListComponent:/*...*/
case FundamentalComponent:/*...*/
case ScopeComponent:/*...*/
case Block:/*...*/
}
}
appendAllChildren
- 获取当前节点的子级节点,进入
while
循环 - 判断是否是普通元素或文本节点
- 如果是,调用
appendInitialChild
将子级追加到父级中 - 如果不是,则将其视为组件,获取组件的第一个子元素,进入下一个循环
- 组件本身不能转换为真实 DOM 元素
- 如果是,调用
- 子级追加到父级后,判断是否有兄弟节点
- 如果有,给兄弟节点指定父节点
return
,并获取兄弟节点,进入下一个循环 - 如果没有,则判断是否有父级,或父级为最初传入的节点
- 如果满足条件,则表示已将所有子级追加到父级中
- 如果不满足,则可能是组件,获取当前节点的父节点,继续判断它的兄弟节点
- 如果有,给兄弟节点指定父节点
// packages\react-reconciler\src\ReactFiberCompleteWork.js
// 将所有子级追到到父级中
appendAllChildren = function (
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
// 获取子级
let node = workInProgress.child;
// 如果子级不为空 执行循环
while (node !== null) {
// 如果 node 是普通 ReactElement 或者为文本
if (node.tag === HostComponent || node.tag === HostText) {
// 将子级追加到父级中
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 如果 node 不是普通 ReactElement 又不是文本
// 将 node 视为组件, 组件本身不能转换为真实 DOM 元素
// 获取到组件的第一个子元素, 继续执行循环
node.child.return = node;
node = node.child;
continue;
}
// 如果 node 和 workInProgress 是同一个节点
// 说明 node 已经退回到父级 终止循环
// 说明此时所有子级都已经追加到父级中了
if (node === workInProgress) {
return;
}
// 处理子级节点的兄弟节点
while (node.sibling === null) {
// 如果节点没有父级或者节点的父级是自己, 退出循环
// 说明此时所有子级都已经追加到父级中了
if (node.return === null || node.return === workInProgress) {
return;
}
// 更新 node
node = node.return;
}
// 更新父级 方便回退
node.sibling.return = node.return;
// 将 node 更新为下一个兄弟节点
node = node.sibling;
}
};
收集所有要执行 DOM 操作的 fiber 节点对象
React 是使用一个链表结构收集所有要执行 DOM 操作的 fiber 节点对象的。
- firstEffect 存储第一个要执行的操作
- lastEffect 存储最后一个要执行的操作
- nextEffect 存储下一个要执行的操作
- 存储在 lastEffect 下
firstEffect 和 lastEffect 如何定义的为作解析。
// 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上
// 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
if (returnFiber.firstEffect === null) {
// first
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
// next
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
// last
returnFiber.lastEffect = workInProgress.lastEffect;
}