摘要
本文聚焦 React 框架 render 阶段的 completeWork 函数,阐述不同 fiber.tag 处理逻辑、mount 和 update 时的 props 处理、DOM 节点操作,还介绍了 effectList 的作用,最后提及渲染阶段结束后的流程走向。
一、整体流程背景
在 React 组件的 render 阶段包含 beginWork 与 completeWork。之前了解到 beginWork 会创建子 Fiber 节点,这部分可能存在 effectTag。现在重点关注 completeWork 的工作内容。
二、completeWork 函数基本结构
completeWork函数接收current、workInProgress和renderLanes作为参数。它会根据workInProgress.tag的不同调用不同处理逻辑。
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:
return null;
case ClassComponent: {
//...省略
return null;
}
case HostRoot: {
//...省略
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
//...省略
return null;
}
//...省略
三、重点关注 HostComponent
(一)处理逻辑判断依据
对于HostComponent(原生 DOM 组件对应的 Fiber 节点),和beginWork一样,根据current === null判断是mount还是update。并且针对HostComponent在判断update时,还需考虑workInProgress.stateNode!= null(即该 Fiber 节点是否存在对应的 DOM 节点)。
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current!== null && workInProgress.stateNode!= null) {
// update 的情况
//...省略
} else {
// mount 的情况
//...省略
}
return null;
}
(二)update 时的操作
- 主要操作内容
当
update时,Fiber 节点已存在对应 DOM 节点,无需生成 DOM 节点,主要处理props。例如注册onClick、onChange等回调函数,处理style prop、DANGEROUSLYSETINNERHTML prop(dangerouslysetinnerhtml)、children prop等。 - updateHostComponent 方法
最主要逻辑是调用
updateHostComponent方法。在updateHostComponent内部,被处理完的props会被赋值给workInProgress.updateQueue,并最终在commit阶段渲染到页面上(updatePayload为数组形式,偶数索引为变化的prop key,奇数索引为变化的prop value)。
if (current!== null && workInProgress.stateNode!= null) {
// update 的情况
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance
);
}
(三)mount 时的操作
- 主要操作内容
mount时主要有三个逻辑:为Fiber节点生成对应的DOM节点;将子孙DOM节点插入刚生成的DOM节点中;进行与update逻辑中的updateHostComponent类似的props处理过程。 - 具体代码实现
// mount 的情况
//...省略服务端渲染相关逻辑
const currentHostContext = getHostContext();
// 为 fiber 创建对应 DOM 节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
// 将子孙 DOM 节点插入刚生成的 DOM 节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM 节点赋值给 fiber.stateNode
workInProgress.stateNode = instance;
// 与 update 逻辑中的 updateHostComponent 类似的处理 props 的过程
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext
)
) {
markUpdate(workInProgress);
}
因为completeWork属于“归”阶段调用的函数,每次调用appendAllChildren都会将已生成的子孙 DOM 节点插入当前生成的 DOM 节点下。当“归”到rootFiber时,就有一个构建好的离屏 DOM 树。而mount时只会在rootFiber存在Placement effectTag,commit阶段可通过一次插入 DOM 操作(对应一个Placement effectTag)将整棵 DOM 树插入页面。
四、effectList 相关
- 作用
commit阶段需要依据effectList找到所有有effectTag的Fiber节点并执行相应操作。 - 构建过程
为避免
commit阶段遍历Fiber树寻找effectTag!== null的Fiber节点的低效操作,在completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在effectList单向链表中。effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。在“归”阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成以rootFiber.firstEffect为起点的单向链表。
五、流程结尾
渲染阶段全部完成后,在performSyncWorkOnRoot函数中fiberRootNode被传递给commitRoot方法,开启commit阶段工作流程。
commitRoot(root);
