React 协调器 render 阶段

先来看个全局变量 executionContext

executionContext

executionContext 标记当前执行环境,比如当前是 render 阶段或者 commit 阶段。

类型是 number,取值有 4 种:

js 复制代码
type ExecutionContext = number;

export const NoContext = /*             */ 0b000;
const BatchedContext = /*     批量/同步更新          */ 0b001;
export const RenderContext = /*    render 阶段     */ 0b010;
export const CommitContext = /*   commit 阶段      */ 0b100;

初始值为 NoContext

js 复制代码
// 参考 react/packages/react-reconciler/src/ReactFiberWorkLoop.js
let executionContext: ExecutionContext = NoContext; 

两个阶段

和 render 阶段平行的还有个 commit 阶段。

  1. render 阶段,构建 fiber 树 VDOM
  2. commit 阶段,将 VDOM 转换成 DOM

两者入口如下:

js 复制代码
// 这是每个并发任务的入口点,即通过 Scheduler 的所有内容
function performConcurrentWorkOnRoot(
  root: FiberRoot,
  didTimeout: boolean,
): RenderTaskFn | null {
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // 没有更新
  if (lanes === NoLanes) {
    // Defensive coding. This is never expected to happen.
    return null;
  }

  // 在某些情况下,我们会禁用时间切片:如果 work 过长时间做计算(为了防止饥饿而将其视为"过期"的 work),或者我们处于默认启用同步更新模式。
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);

  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes) // 使用时间切片
    // 1. render, 构建fiber树VDOM(beginWork|completeWork)
    : renderRootSync(root, lanes); // 不用时间切片

  // 2. commit, VDOM->DOM
  commitRoot(root);
}

renderRootSync

renderRootSync 是 render 阶段入口,包含 4 个步骤:

  1. render 阶段开始
  2. workInProgressTransitions 赋值
  3. 初始化
  4. 遍历构建 fiber 树

详细代码如下:

js 复制代码
function renderRootSync(root: FiberRoot) {
  // 1. render阶段开始
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext; // 注意这里不能用等号,如果使用等号,会覆盖其他 execution,比如如果是批量更新,如果直接覆盖,批量更新就没有了
  // 2. workInProgressTransitions 赋值
  workInProgressTransitions = getTransitionsForLanes(root, lanes);
  // 3. 初始化
  prepareFreshStack(root, lanes);

  // 4. 遍历构建 fiber 树
  workLoopSync();

  // 重置 Context 的相关值,入 currentlyRenderingFiber 等
  resetContextDependencies();

  // 4. render结束,重置 executionContext
  executionContext = prevExecutionContext;
  
  workInProgressRoot = null; // 当前正在工作的 root
  workInProgressRootRenderLanes = NoLanes;

  // 防止有新的更新,再遍历一边更新队列
  finishQueueingConcurrentUpdates();

  return workInProgressRootExitStatus;
}

function prepareFreshStack(root: FiberRoot): Fiber {
  root.finishedWork = null;

  workInProgressRoot = root; // FiberRoot
  const rootWorkInProgress = createWorkInProgress(root.current, null); // Fiber
  if (workInProgress === null) {
    workInProgress = rootWorkInProgress; // Fiber
  }

  // 把 concurrentQueues 的内容添加到 fiber 的 queue 中,即给 fiber 的 lanes、childLanes 赋值
  finishQueueingConcurrentUpdates();

  return rootWorkInProgress;
}

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork: Fiber) {
  const current = unitOfWork.alternate;
  // 1. beginWork
  // 更新当前 fiber,比如 props/state 更新,生命周期函数执行、Hooks 函数执行等。
  // 返回下一个 fiber。
  let next = beginWork(current, unitOfWork);
  //  把 pendingProps 更新到 memoizedProps
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  // 1.1 执行自己
  // 1.2 (协调,bailout)返回子节点

  if (next === null) {
    // 没有产生新的work
    // 如果不再产生新的 work,那么当前 work 结束
    // 2. completeWork,比如生成原生节点,放在 fiber 的 stateNode 属性上
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  // 当前 Fiber 为 null
  ReactCurrentOwner.current = null
}

// 深度优先遍历,子节点、兄弟节点、叔叔节点、爷爷的兄弟节点...(王朝的故事)
function completeUnitOfWork(unitOfWork: Fiber) {
  let completedWork = unitOfWork;

  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    let next = completeWork(current, completedWork);
    if (next !== null) {
      workInProgress = next;
      return;
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }

    completedWork = returnFiber as Fiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}
相关推荐
Byron070719 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js
css趣多多19 小时前
地图快速上手
前端
zhengfei61119 小时前
面向攻击性安全专业人员的一体化浏览器扩展程序[特殊字符]
前端·chrome·safari
码丁_11720 小时前
为什么前端需要做优化?
前端
Mr Xu_20 小时前
告别硬编码:前端项目中配置驱动的实战优化指南
前端·javascript·数据结构
Byron070721 小时前
从 0 到 1 搭建 Vue 前端工程化体系:提效、提质、降本实战落地
前端·javascript·vue.js
哆啦code梦21 小时前
前端存储三剑客:localStorage、sessionStorage与Cookie解析
前端·前端存储
徐小夕@趣谈前端21 小时前
Web文档的“Office时刻“:jitword共建版2.0发布!让浏览器变成本地生产力
前端·数据结构·vue.js·算法·开源·编辑器·es6
Data_Journal21 小时前
如何使用 Python 解析 JSON 数据
大数据·开发语言·前端·数据库·人工智能·php
德育处主任Pro21 小时前
纯前端网格路径规划:PathFinding.js的使用方法
开发语言·前端·javascript