让我们再回顾一下performUnitOfWork 的过程
function performUnitOfWork(unitOfWork){
/* 执行 */
let next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
/* 优先将 next 赋值给 workInProgress,如果没有 next,那么调用 completeUnitOfWork 向上归并处理。 */
if (next = = = null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
performUnitOfWork 是什么来着?他是调和每一个 workInProgress fiber的入口,workInProgress只要不为空,就会一直执行下去
function workLoopSync() {
/* 循环执行 performUnitOfWork,一直到 workInProgress 为空 */
while (workInProgress ! = = null) {
performUnitOfWork(workInProgress);
}
}
好,那么 workInProgress fiber 是怎么流转的,就是通过 beginWork() 的返回值,执行 beginWork()会返回 workInProgress fiber的子fiber,再执行 workInProgress = next;就这样一直深度优先遍历到叶子结点,直接看下图:
graph TD
A[workLoopSync 启动] --> B{workInProgress 非空?}
B -->|是| C[调用 performUnitOfWork]
C --> D[执行 beginWork]
D --> E{beginWork 返回<br>子节点 next?}
E -->|返回子节点| F[workInProgress = next]
F --> B
E -->|无子节点| G[completeUnitOfWork]
G --> H{有兄弟节点?}
H -->|是| I[workInProgress = 兄弟节点]
I --> B
H -->|无| J[回溯父节点完成]
J --> G
B -->|workInProgress=null| K[结束]
- 子节点获取策略:
// 简化版 beginWork 逻辑 function beginWork(current, workInProgress) { switch (workInProgress.tag) { case FunctionComponent: return updateFunctionComponent(current, workInProgress); case HostComponent: return updateHostComponent(current, workInProgress); // ...其他类型处理 } }
- 无子节点时的回溯:
- 当
beginWork
返回null
时触发completeUnitOfWork
- 优先检查兄弟节点 (
workInProgress.sibling
) - 无兄弟节点时回溯父节点,直到根节点
📌 重要特征
-
不可逆流程
workInProgress
指针一旦离开父节点就不会再回到该节点(直到后续提交阶段) -
节点复用机制
beginWork
通过current
树(当前UI树)复用Fiber节点,减少创建开销 -
副作用标记
在beginWork
过程中会标记节点的副作用(如增删改)-- flag标记--记住这里,一会要考
completeWork 阶段
先看代码
function completeUnitOfWork(unitOfWork){
let completedWork = unitOfWork;
do {
/* 向上找到父级 fiber */
const current = completedWork.alternate;
const returnFiber = completedWork.return;
/* 执行 completeWork */
completeWork(current, completedWork, subtreeRenderLanes);
const siblingFiber = completedWork.sibling;
/* 当前 fiber 如果有兄弟节点,那么停止循环,将当前兄弟节点赋值给 workInProgress,然后这个节点会进入接下来的 workLoop 中。*/
if (siblingFiber ! = = null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
}while (completedWork! = = null);
}
首先来看流程:
1.会先让叶子结点fiber 执行completeWork,
2.当前叶子 fiber 如果有兄弟节点,则停止循环,将当前兄弟节点赋给 workInProgress,然后这个节点会退出completeUnitOfWork流程,进入下一个performUnitOfWork中,进入接下来的 workLoop 中。
3.如果没有兄弟,就把 completedWork 指向 叶子结点的父fiber,继续让 父fiber 执行 completeWork
graph TD
A["开始completeUnitOfWork"] --> B["当前节点:completedWork = unitOfWork"]
B --> C["执行completeWork处理当前节点"]
C --> D{"检查兄弟节点siblingFiber是否存在?"}
D -->|存在| E["workInProgress = siblingFiber<br/>结束当前回溯"]
D -->|不存在| F["completedWork = returnFiber<br/>workInProgress = returnFiber"]
F --> G{"returnFiber为null?"}
G -->|否| C
G -->|是| H["结束整个工作循环"]
classDef highlight fill:#ffcc00,stroke:#333;
class E highlight;
核心流程completeWork
还是上代码
function completeWork(current,workInProgress,renderLanes){
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
/* 如果是类组件,那么执行 bubbleProperties */
case ClassComponent: {
bubbleProperties(workInProgress);
return null;
}
/* DOM 元素 */
case HostComponent: {
if (current ! = = null && workInProgress.stateNode! = null) {
/* 更新流程 */
updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance)
if (current.ref! = = workInProgress.ref) {
/* 当 ref 变化,会重新标记 ref */
markRef(workInProgress);
}
}else{ /* 初始化流程 */
if (wasHydrated) {
// 服务端渲染,这里暂时不考虑 }else{
/* 创建 DOM 元素 */
const instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);
/* 插入真实的 DOM 元素 */
appendAllChildren(instance, workInProgress, false, false);
}
}
bubbleProperties(workInProgress);
return null;
}
// ...省略其他 fiber 的流程 }
}
graph TD
A["开始 completeWork"] --> B["获取 newProps"]
B --> C{"根据 tag 分支"}
C --> |ClassComponent| D["调用 bubbleProperties"]
D --> Z["返回 null"]
C --> |HostComponent| E{"是更新流程?"}
E --> |是| F["updateHostComponent"]
F --> G{"ref 变化?"}
G --> |是| H["markRef"]
G --> |否| I
E --> |否| J{"需要注水?"}
J --> |是| K["服务端渲染"]
J --> |否| L["createInstance"]
L --> M["appendAllChildren"]
H --> I
I --> N["调用 bubbleProperties"]
K --> N
M --> N
N --> Z
C --> |其他| X["其他处理"]
X --> Z
这里留意一个重点:bubbleProperties -- 设置 subtreeFlags,
还记得 我们之前介绍过的 childLanes 吗?从更新的子组件一直向上标记 childLanes,方便寻找真正需要更新的子组件
其实 subtreeFlags 和 childLanes是异曲同工之妙,只不过 subtreeFlags标记子树中的副作用操作,方便 commit 阶段快速处理
🧩 设计哲学统一性
设计原则 | subtreeFlags实现 | childLanes实现 |
---|---|---|
最小化工作范围 | 通过副作用的位掩码标识 | 通过优先级车道标识 |
树状结构聚合 | 都采用自底向上的状态聚合,父节点聚合子树的副作用状态 | |
增量更新支持 | 识别哪些子树需要DOM操作 | 识别哪些子树需要协调过程 |
高效状态传播 | 位运算快速合并状态 | 位运算快速合并优先级 |
并发模式基础 | 提交阶段安全应用副作用 | 协调阶段支持任务中断与恢复 |
总结
下面这两句话,请你背下来,并且是充分理解后背下来!
beginWork:找到发生更新的类组件,执行类组件和函数组件获得新 element,diff 生成新的 fiber。根据不同类型的 fiber,处理不同的逻辑。fiber 中有需要更新的地方,打上标志。
completeWork:根据子代 fiber 的标志,形成父级的 subtreeFlags 属性。初始化阶段会创建元素,建立 DOM 树结构