欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

React 源代码分析(3):WorkLoop 工作流程和源代码

最编程 2024-04-21 21:19:45
...

前面介绍了 React 的整体架构和 React 当中重要的数据结构 Fiber,以及 Fiber 的源头 element 对象和 FiberRoot 根对象,这一章开始讲解 React 核心工作过程的源码。

在讲解源码的过程中,最常提到的两个全局变量:

  • current,虚拟 DOM 树中,diff 算法遍历到的旧 Fiber 节点(不同于 fiberRoot.current);
  • workInProgress,将要替换 current 的新 Fiber 节点。

假设 React Reconcile 现在正处在一个组件的更新阶段,React 已经通过 useState 的 dispatch,再调用 scheduleUpdateOnFiber 创建了一次更新,更新的过程是这样的:

Fiber工作流.png

图中 Fiber1 就是这次更新的组件在 current 树中对应的虚拟DOM,scheduleUpdateOnFiber 方法作用于它。第五步替换发生在 commit 阶段,不包含在 workLoop 中。

第一轮,完成1和2,生成 new Fiber1;第二轮,完成3和4,生成 new Fiber2,如果还有子组件和兄弟组件,则还会有下一轮。React 就是这样将一次对比与生成的过程看成一次 work,然后不断地循环,这就是 workLoop。

当前这轮 work 当中生成的 Fiber 称为 workInProgressFiber,源码中常常简称 workInProgress,显然,每一轮它都会指向不同的新 Fiber。

Concurrent 模式引入后 workLoop 有两种,一种是旧的 workLoopSync,一种是新的 workLoopConcurrent,唯一区别就是 workLoopConcurrent 可以被 Schedule 暂停。

function workLoopSync() {
    while (workInProgress !== null) {
        // workInProgress 全局属性每次循环都会被修改
        performUnitOfWork(workInProgress);
    }
}
...

import { shouldYield } from './Scheduler';
function workLoopConcurrent() {
    // Perform work until Scheduler asks us to yield
    while (workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress);
    }
}

performUnitOfWork

  • performUnitOfWork 首先会执行 beginWork 生成当前 workInProgress 的所有第一级子节点,并返回第一个子节点,此时第一个子节点会成为下一个 workInProgress;如果没有子节点则返回 null;
  • beginWork 返回值不为 null,则一直循环执行 beginWork,直至返回 null;
  • beginWork 返回值为 null,则执行 completeUnitOfWork,对当前 workInProgress 的所有第一级子节点进行 FLAG 类属性向上冒泡操作,并把其 return 属性指向 workInProgress
const current = workInProgress.alternate;

let next;
// 主要作用是为`workInProgress`生成子节点
next = beginWork(current, workInProgress, subtreeRenderLanes);

workInProgress.memoizedProps = workInProgress.pendingProps;
if (next === null) {
    // 主要作用是定义 workInProgress 关于子节点的 flag 属性(属性冒泡),
    // 并把子节点 return 属性指向 workInProgress
    completeUnitOfWork(workInProgress);
} else {
    workInProgress = next;
}
  • 进入 completeUnitOfWorkworkInProgress 如果没有子节点则返回下一个兄弟节点,此时下一个兄弟节点会成为新的 workInProgress,重新进入 beginWork 阶段;
  • 进入 completeUnitOfWorkworkInProgress 如果没有下一个兄弟节点,则继续对其父节点进行 completeUnitOfWork 过程。
let completedWork = workInProgress
do {
    // completedWork(workInProgress)在 current 树中对应的节点
    const current = completedWork.alternate;
    // completedWork(workInProgress)父节点
    const returnFiber = completedWork.return;

    let next = completeWork(current, completedWork, subtreeRenderLanes);
    if (next !== null) {
        // 如果 next 存在,则将 workInProgress 指向 next
        workInProgress = next;
        return;
    }
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
        // next 为 null,siblingFiber 不为 null,
        // 则将 workInProgress 指向 siblingFiber
          workInProgress = siblingFiber;
          return;
    }
    // 该 workInProgress 没有兄弟节点,则将 completeWork 指向其父节点
    // 并继续这个循环
    completedWork = returnFiber;
    workInProgress = completedWork;
} while (completedWork !== null);

总结

  • performUnitOfWork 会循环执行 beginWorkcompleteWork
  • beginWork 只负责生成子节点,并返回第一个子节点;completeWork 只负责子节点 flag 类属性的冒泡;
  • Fiber 树的遍历需要依靠 completeUnitOfWork 调度 beginWorkcompleteWork 两个函数;
  • workLoop 借助 workInProgress 这个变量,根据更新的组件在内存中勾勒出一棵局部的新 Fiber 树。

图解

假设有以下 React Fiber 树,其中每个圆角方框都代表一个 Fiber 节点;接下来将截取其生成过程的一部分来说明 beginWorkcompleteWork 运作模式:

workLoopSync.png

生成过程:椭圆代表当前 workInProgress 所指向的 Fiber 节点;performUnitOfWork 带着 App 进入了 beginWork 阶段,beginWork 阶段创建了 AC,并将 Appchild 属性指向 ACAsibling 属性指向 C

workLoopSync_1 .png

performUnitOfWork 第一次执行,beginWork 返回 A 之后,workInProgress 指向 A,因此 workInProgress !== nullperformUnitOfWork 带着 A 进入 beginWork 阶段,beginWork 阶段创建了 B,并把 Achild 属性指向 B;

workLoopSync_2.png

performUnitOfWork 第二次执行,beginWork 返回 B 之后,workInProgress 指向 B,因此 workInProgress !== nullperformUnitOfWork 带着 B 进入 beginWork 阶段,beginWork 阶段根据 tag 属性做一些特殊处理,一直到 beginWork 内部 reconcileChildFibers 函数执行时,判断无子节点并返回 null;

workLoopSync_3.png

此时生成的 Fiber 树结构为:

workLoopSync_4.png

因为 beginWork 返回 null,此时 workInProgress 仍指向 BperformUnitOfWork 会调用 completeUnitOfWork 函数,completeWork 函数带着 B 节点进入 completeWork 阶段,completeWork 函数多数情况只会冒泡子节点属性并返回 null,因为 B 没有子节点,所以直接返回 null;

completeUnitOfWork 内部检查 B 节点是否有兄弟节点,发现 B 节点没有兄弟节点,因此completeWork 函数带着 A 节点进入 completeWork 阶段,将其子节点部分属性冒泡到 A 节点,并将其所有子节点 return 属性指向 A

对节点 A completeWork 过程结束后,completeUnitOfWork 查询 A 是否有兄弟节点,发现有兄弟节点 C,因此返回 C

workLoopSync_5.png

completeUnitOfWork 返回节点 C,因此 workInProgress 指向 CC 会跟之前 A 节点一样,经历同样的 beginWorkcompleteWork 过程,并且因为 C 没有兄弟节点,因此下一个进行 completeWork 的是它的父节点 AppApp 进入 completeWork 之前生成的 Fiber 树结构为:

workLoopSync_6.png

App 进入 completeWork 阶段,冒泡子节点 AC 部分属性,并将 AC 节点 return 属性指向 App

workLoopSync_7.png

推荐阅读