React源码系列(七):render阶段completeWork流程解析
前言
这是React源码系列专栏的第六篇文章,预计写10篇左右,之前的文章请查看文末,通过本专栏的学习,相信大家可以快速掌握React源码的相关概念以及核心思想,向成为大佬的道路上更近一步; 本章我们学习render阶段completeWork流程,本系列源码基于v18.2.0版本;
render阶段的主要工作是构建Fiber树和生成effectList。
我们接着上一章继续讲解completeWork流程。
调用completeWork
performUnitOfWork 每次会尝试调用 beginWork 来创建当前节点的子节点,若创建出的子节点为空,则说明当前节点是一个叶子节点。按照深度优先遍历的原则,当遍历到叶子节点时,递阶段就结束了也就是beginWork阶段完成。随之而来进入归阶段,也就是 completeUnitOfWork,执行当前节点对应的 completeWork 逻辑。
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;
}
completeUnitOfWork中会调用completeWork的逻辑,completeUnitOfWork的逻辑在下面会讲。
function completeUnitOfWork(unitOfWork: Fiber): void {
/** ...省略... **/
next = completeWork(current, completedWork, subtreeRenderLanes);
/** ...省略... **/
}
进入completeWork流程
我们对照着下面这张completeWork的图来梳理一下流程。
completeWork主要工作是处理fiber的props、创建dom,根据 workInProgress 节点的 tag 属性的不同,进入不同的 DOM 节点的创建、处理逻辑,向上冒泡副作用操作。我们提取一下主要的逻辑(看注释部分即可)。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
// 如果是函数组件或类组件进入此段逻辑
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress); // 将flags冒泡到父节点
return null;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
bubbleProperties(workInProgress);
return null;
}
// 我们直接挑HostComponent这段逻辑讲解
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
// 判断 current 节点是否存在,因为目前是挂载阶段,因此 current 节点是不存在的
if (current !== null && workInProgress.stateNode != null) {
// update
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// mount
// 为 DOM 节点的创建做准备
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
// 判断是否是服务端渲染,这里不重点关注
if (wasHydrated) {
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext
)
) {
markUpdate(workInProgress);
}
} else {
// 这一步很关键, createInstance 的作用是创建 DOM 节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
// appendAllChildren 会尝试把上一步创建好的 DOM 节点挂载到 DOM 树上去
appendAllChildren(instance, workInProgress, false, false);
// stateNode 用于存储当前 Fiber 节点对应的 DOM 节点
workInProgress.stateNode = instance;
if (
// finalizeInitialChildren 用来为 DOM 节点设置属性
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext
)
) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.'
);
}
这里需要注意一下执行bubbleProperties方法,bubbleProperties 根据fiber.child及fiber.child.sibling更新subtreeFlags和childLanes, 主要是为了标记子树有没有更新, 这样可以通过 fiber.subtreeFlags 快速判断子树是否有副作用钩子,不需要深度遍历. 在React18版本使用subtreeFlags替换了finishWork.firstEffect的副作用链表, 操作主要发生在bubbleProperties函数中。 completeWork 这段代码逻辑包含以下几点:
- update时,调用updateHostComponent处理props(包括onClick、style、children ...),并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上。
- mount时,调用createInstance创建dom,将后代dom节点插入刚创建的dom中,调用finalizeInitialChildren处理props,简单总结为以下3点。
- 创建DOM 节点(CreateInstance)
- 将 DOM 节点插入到 DOM 树中(AppendAllChildren)
- 为 DOM 节点设置属性(FinalizeInitialChildren)
completeUnitOfWork
completeUnitOfWork 的作用是开启一个循环,它会反复做下面几件事。
- 针对传入的当前节点,调用 completeWork;
- 向上冒泡副作用生成subtreeFlags,也就是completeWork中的bubbleProperties逻辑;
- 以当前节点为起点,循环遍历其兄弟节点及其父节点。当遍历到兄弟节点时,将 return 掉当前调用,触发兄弟节点对应的 performUnitOfWork 逻辑;而遍历到父节点时,则会直接进入下一轮循环,也就是重复上两步的逻辑。
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
/** 省略 **/
next = completeWork(current, completedWork, subtreeRenderLanes);
/** 省略 **/
const siblingFiber = completedWork.sibling;
// 若兄弟节点存在
if (siblingFiber !== null) {
// 将 workInProgress 赋值为当前节点的兄弟节点
workInProgress = siblingFiber;
// 将正在进行的 completeUnitOfWork 逻辑 return 掉
return;
}
// 若兄弟节点不存在,completeWork 会被赋值为 returnFiber,也就是当前节点的父节点
completedWork = returnFiber;
// 这一步与上一步是相辅相成的,上下文中要求 workInProgress 与 completedWork 保持一致
workInProgress = completedWork;
} while (completedWork !== null);
}
小结
本章我们学习了render阶段completeWork流程,接下来的文章将进入diff源码分析,欢迎继续跟随本专栏一起学习;
参考链接
React源码系列
转载自:https://juejin.cn/post/7210048692622835770