一图看懂 React 源码中的同步更新逻辑

开发 前端
同步更新的过程比较简单,从 ScheduleUpdateOnFiber 到 BeginWork 这中间的流程里,大多数逻辑都在进行各种不同情况的判断,因此源码看上去比较吃力,实际逻辑并不是很重要,简单了解即可。

在 React 源码中,scheduleUpdateOnFiber 是所有任务的唯一入口方法。我们前面分析 useState 的实现原理章节中,我们可以清晰的知道,当我们调用 dispatchSetState 时,最终会调用该入口方法。

scheduleUpdateOnFiber 主要用于触发一个 Fiber 节点上的调度更新任务,该函数里主要有两个核心逻辑。

// Mark that the root has a pending update.
// 标记 root 上有一个更新任务
markRootUpdated(root, lane, eventTime);

ensureRootIsScheduled(root, eventTime);

markRootUpdated 的逻辑如下,简单了解一下即可。

export function markRootUpdated(
  root: FiberRoot,
  updateLane: Lane,
  eventTime: number,
) {

  // 设置本次更新的优先级
  root.pendingLanes |= updateLane;

  // 重置 root 应用根节点的优先级
  if (updateLane !== IdleLane) {  
    // 由 Suspence 而挂起的 update 对应的 lane 集合
    root.suspendedLanes = NoLanes; 
    // 由请求成功,Suspence 取消挂起的 update 对应的 Lane 集合
    root.pingedLanes = NoLanes; 
  }

  const eventTimes = root.eventTimes;
  const index = laneToIndex(updateLane);
  eventTimes[index] = eventTime;
}

ensureRootIsScheduled 的主要目的要确保 root 根节点被调度。在该逻辑中,会根据 root.pendingLanes 信息计算出本次更新的 Lanes: nextLanes。

const nextLanes = getNextLanes(
  root,
  root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);

然后根据 nextLanes 计算出本批次集合中优先级最高的 Lane,作为本地任务的优先级。

// We use the highest priority lane to represent the priority of the callback.
const newCallbackPriority = getHighestPriorityLane(nextLanes);

后续的逻辑就是取出当前已存在的调度优先级,与 newCallbackPriority 进行对比,根据对比结果来执行不同的更新方法。当该值等于 SyncLane 时,表示为同步更新。

同步优先级例如点击事件。

然后会判断是否支持微任务更新,如果不支持最后会执行 scheduleCallback。

if (newCallbackPriority === SyncLane) {
  if (supportsMicrotasks) {
    // Flush the queue in a microtask.
    if (__DEV__ && ReactCurrentActQueue.current !== null) {
      // Inside `act`, use our internal `act` queue so that these get flushed
      // at the end of the current scope even when using the sync version
      // of `act`.
      ReactCurrentActQueue.current.push(flushSyncCallbacks);
    } else {
      scheduleMicrotask(() => {
        // In Safari, appending an iframe forces microtasks to run.
        // https://github.com/facebook/react/issues/22459
        // We don't support running callbacks in the middle of render
        // or commit so we need to check against that.
        if (
          (executionContext & (RenderContext | CommitContext)) ===
          NoContext
        ) {
          // Note that this would still prematurely flush the callbacks
          // if this happens outside render or commit phase (e.g. in an event).
          flushSyncCallbacks();
        }
      });
    }
  } else {
    // Flush the queue in an Immediate task.
    scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
  }
}

scheduleSyncCallback 的逻辑,也就是同步任务的调度非常简单,就是将执行同步任务的回调添加到一个同步队列 syncQueue 中。

export function scheduleSyncCallback(callback: SchedulerCallback) {
  // Push this callback into an internal queue. We'll flush these either in
  // the next tick, or earlier if something calls `flushSyncCallbackQueue`.
  if (syncQueue === null) {
    syncQueue = [callback];
  } else {
    // Push onto existing queue. Don't need to schedule a callback because
    // we already scheduled one when we created the queue.
    syncQueue.push(callback);
  }
}

这里的 callback 是之前传入的 performSyncWorkOnRoot,这是用来执行同步更新任务的方法。他的逻辑主要包括:

  • 调用 renderRootSync,该方法会执行 workLoopSync,最后生成 Fiber true。
  • 将创建完成的 Fiber tree 挂载到 root 节点上。
  • 最后调用 commitRoot,进入 commit 阶段修改真实 DOM。
function performSyncWorkOnRoot(root) {
  ...
  let exitStatus = renderRootSync(root, lanes);
  
  ...
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  commitRoot(
    root,
    workInProgressRootRecoverableErrors,
    workInProgressTransitions,
  );

  ensureRootIsScheduled(root, now());

  return null;
}

workLoopSync 的逻辑也非常简单,如下:

function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

在 performUnitOfWork 中,会调用 beginWork 方法开始创建 Fiber 节点。

var next = beginWork(
  current, 
  unitOfWork, 
  subtreeRenderLanes
);

总结

同步更新的过程比较简单,从 scheduleUpdateOnFiber 到 beginWork 这中间的流程里,大多数逻辑都在进行各种不同情况的判断,因此源码看上去比较吃力,实际逻辑并不是很重要,简单了解即可,重要的是 beginWork 创建 Fiber 节点的方法,这跟我们之前文章里提到过的优化策略是一致的。

责任编辑:姜华 来源: 这波能反杀
相关推荐

2017-04-12 11:15:52

ReactsetState策略

2023-11-28 09:44:10

2023-04-07 08:02:54

源码位逻辑运算符

2023-11-01 09:00:14

存储Docker服务器

2020-05-20 14:25:45

Reactreact.js前端

2021-01-01 09:04:44

Linux内核变量

2019-06-26 19:18:05

Windows 10系统更新微软

2023-05-17 19:30:52

企业

2024-02-21 14:06:00

Linux文件系统

2015-03-10 10:15:27

AppleWatch开发Swift

2024-02-19 14:58:34

编程范式开发

2024-01-24 09:14:27

编程范式数据

2021-10-11 09:41:20

React位运算技巧前端

2019-11-24 19:17:06

MySQL异步复制全同步复制

2021-04-26 09:22:07

ASP.NET Cor服务周期

2019-03-18 15:00:48

SQLJoin用法数据库

2020-10-21 08:38:47

React源码

2018-05-18 18:09:44

人工智能

2024-03-26 09:16:12

网络架构图AWS

2024-05-07 08:49:45

微服务架构模式
点赞
收藏

51CTO技术栈公众号