Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • plan 计划
  • 基础

  • 调和(Reconciliation)

    • 开始上手
    • Fiber 与 Reconciliation
    • ReactChildFiber 的创建
    • expirationTime与优先级
    • scheduleWork与调度过程
      • 目录
      • scheduleWork:为 Fiber 调度更新
      • ensureRootIsScheduled:确保 Root 被调度
      • scheduleSyncCallback:同步渲染的调度
      • scheduleCallback:异步渲染的调度
      • schedulePendingInteractions 方法:追踪调度过程
      • performSyncWorkOnRoot:同步任务调度更新
      • performConcurrentWorkOnRoot:异步任务调度更新
      • flushSyncCallbackQueue
      • 小结
    • Lane 与优先级
  • 调度器(Scheduler)

  • 更新器(Updater)

  • 渲染器(Render)

  • hooks原理

  • 总结

  • React源码漂流记

  • react
  • 调和(Reconciliation)
jonsam
2022-04-14
目录

scheduleWork与调度过程

# 目录

  • 目录
  • scheduleWork:为 Fiber 调度更新
    • markUpdateTimeFromFiberToRoot:更新 Fiber Tree 的 expirationTime
    • checkForNestedUpdates: 检查嵌套更新
    • 嵌套更新的层级数是如何计算的?
    • checkForInterruption
    • recordScheduleUpdate:记录调度更新的阶段
  • ensureRootIsScheduled:确保 Root 被调度
  • scheduleSyncCallback:同步渲染的调度
  • scheduleCallback:异步渲染的调度
  • schedulePendingInteractions 方法:追踪调度过程
  • performSyncWorkOnRoot:同步任务调度更新
  • performConcurrentWorkOnRoot:异步任务调度更新
  • flushSyncCallbackQueue
  • 小结

scheduleWork 是 react 调度的起点。 scheduleWork(fiber, expirationTime) 传入 fiber 和 expirationTime,可见 fiber 更新的调度是根据 expirationTime 来处理的。

在 react-reconciler/src/ReactFiberWorkLoop.js 中函数 scheduleUpdateOnFiber 实际上就是 scheduleWork。

可以看到现在的 ReactFiberWorkLoop 还在 react-reconciler 包中,其实这里是调度的入口,是调和器与调度器交互的入口。在这里调和器将对 fiber 上的调度追溯到 HostFiberRoot(Fiber 的根节点),在 HostFiberRoot 根据 expirationTime 优先级层级区分为同步调度和异步调度,如果是同步调度(比如 FirstRender 阶段),将跳过调度器直接执行后续的更新流程,只有异步调度或者说当前已经处于 commit 或者 render 状态的同步调度才会交给调度器去调度和回调。

下面我们就来看下调和器是如何做好与调度器的交接工作的。

# scheduleWork:为 Fiber 调度更新

scheduleUpdateOnFiber 代码如下:

/**
 * scheduleWork的别名,创建调度任务执行更新
 * @param {*} fiber 
 * @param {*} expirationTime 
 */
export function scheduleUpdateOnFiber(
    fiber: Fiber,
    expirationTime: ExpirationTime,
) {
    // 检查嵌套更新  
    checkForNestedUpdates();
    warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
    // 从 fiber 到 root 标记 expirationTime,找到 root 
    const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
    console.log("调度任务中root是:", root);
    // 关灯的打印情况:
    // 调度任务中root: FiberRootNode {tag: 0, current: FiberNode, containerInfo: div#root, pendingChildren: null, pingCache: null, …}
    // 如果 root 不存在直接退出调度
    if (root === null) {
        warnAboutUpdateOnUnmountedFiberInDEV(fiber);
        return;
    }
    // 检查中断
    checkForInterruption(fiber, expirationTime);
    // 记录调度更新
    recordScheduleUpdate();

    // TODO: computeExpirationForFiber also reads the priority. Pass the
    // priority as an argument to that function and this one.
    // 获得调度器当前回调的优先级
    const priorityLevel = getCurrentPriorityLevel();

    // 同步调度,跳过优先级调度直接执行
    if (expirationTime === Sync) {
        if (
            // 如果是 unbatchedUpdates 且不在 rendering 和 commit 状态
            // Check if we're inside unbatchedUpdates
            // 第一个条件是 executionContext 的值为 LegacyUnbatchedContext,第二个条件是, executionContext 不能处在 RenderContext 或者是 CommitContext 的阶段。
            (executionContext & LegacyUnbatchedContext) !== NoContext &&
            // Check if we're not already rendering
            // 这里表示不是处于 RenderContext 和 CommitContext,因为如果已经在 render 或者 commit 阶段,需要等待下一次
            // 不在 render 阶段或 commit 阶段则直接执行更新
            (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
            // Register pending interactions on the root to avoid losing traced interaction data.
            // 追踪调度过程,包括计数、错误检测
            schedulePendingInteractions(root, expirationTime);

            // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
            // root inside of batchedUpdates should be synchronous, but layout updates
            // should be deferred until the end of the batch.
            // 同步方式渲染 root,在 ReactDOM 中 FiberTree 的初始化会走到这里
            performSyncWorkOnRoot(root);
        } else {
			      // 首次渲染,同步更新,确保root节点被调度
            ensureRootIsScheduled(root);
            // 调度追踪
            schedulePendingInteractions(root, expirationTime);
            // 当前处于空闲状态,可以加工同步回调队列
            if (executionContext === NoContext) {
                // Flush the synchronous work now, unless we're already working or inside
                // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
                // scheduleCallbackForFiber to preserve the ability to schedule a callback
                // without immediately flushing it. We only do this for user-initiated
                // updates, to preserve historical behavior of sync mode.
                // 刷新同步任务队列
                flushSyncCallbackQueue();
            }
        }
    } else {
        // 异步调度
        ensureRootIsScheduled(root);
        schedulePendingInteractions(root, expirationTime);
    }

   // 这里是对离散事件的管理,由映射表 rootsWithPendingDiscreteUpdates 管理
    if (
    (executionContext & DiscreteEventContext) !== NoContext &&
    // Only updates at user-blocking priority or greater are considered
    // discrete, even inside a discrete event.
    (priorityLevel === UserBlockingPriority ||
      priorityLevel === ImmediatePriority)
      // 当前如果处于DiscreteEventContext,且调度器当前的回调优先级为 UserBlockingPriority 或者 ImmediatePriority
      // 离散事件优先级(比如 click)较低
  ) {
    // This is the result of a discrete event. Track the lowest priority
    // discrete update per root so we can flush them early, if needed.
    if (rootsWithPendingDiscreteUpdates === null) {
      // 如果离散表不存在就创建离散更新的映射表
      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
    } else {
      // 当前 HostFiberRoot 上的离散更新
      const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
      // 如果当前 HostFiberRoot 上还没有离散更新,或者这里的离散更新优先级更高,就将之覆盖掉离散表上当前 HostFiberRoot 上的更新。这里不会
      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
        rootsWithPendingDiscreteUpdates.set(root, expirationTime);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

# markUpdateTimeFromFiberToRoot:更新 Fiber Tree 的 expirationTime

该函数用于获得 FiberRoot 对象,算出的本次更新的 expirationTime,更新 Fiber tree 上的 expirationTime。React 的每次更新其实是从整个 Fiber 树的根节点开始调度的。

// 获取root节点,并且给root节点添加标记。
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
  // Update the source fiber's expiration time
  // 同步 fiber 和 fiber.alternate 的 expirationTime
  // 如果 fiber 的 expirationTime 比 expirationTime 低,则说明 fiber 的优先级较低,则把 fiber 的优先级
  // 提高到当前优先级
  if (fiber.expirationTime < expirationTime) {
    fiber.expirationTime = expirationTime;
  }
  let alternate = fiber.alternate;
  if (alternate !== null && alternate.expirationTime < expirationTime) {
    alternate.expirationTime = expirationTime;
  }
  // Walk the parent path to the root and update the child expiration time.
  // 向上追溯到 root 节点并且更新子节点的超时时间,获取 fiber 的父节点
  let node = fiber.return;
  let root = null;
  // 如果 fiber 没有父节点且具有 HostRoot 的标记则次 fiber 就是 FiberRoot。
  if (node === null && fiber.tag === HostRoot) {
    // fiber.return 为空则没有父节点,这里直接找到 root 节点
    root = fiber.stateNode;
  } else {
    // 没有直接获取到 FiberRoot,则向上追溯
    while (node !== null) {
      // 更新 node 和 alternate 的超时时间
      alternate = node.alternate;
      if (node.childExpirationTime < expirationTime) {
        // 更新父节点的childExpirationTime,代表子节点的超时时间
        node.childExpirationTime = expirationTime;
        if (
          alternate !== null &&
          alternate.childExpirationTime < expirationTime
        ) {
          alternate.childExpirationTime = expirationTime;
        }
      } else if (
        alternate !== null &&
        alternate.childExpirationTime < expirationTime
      ) {
        alternate.childExpirationTime = expirationTime;
      }
      // 追溯到 root 节点
      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }
      node = node.return;
    }
  }

  if (root !== null) {
    // root 正是调度更新节点树的根节点
    if (workInProgressRoot === root) {
      // Received an update to a tree that's in the middle of rendering. Mark
      // that's unprocessed work on this root.
      // 标记更新节点树下一次更新时间,workInProgressRootNextUnprocessedUpdateTime 取较大值
      markUnprocessedUpdateTime(expirationTime);

      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        // The root already suspended with a delay, which means this render
        // definitely won't finish. Since we have a new update, let's mark it as
        // suspended now, right before marking the incoming update. This has the
        // effect of interrupting the current render and switching to the update.
        // 根已经延迟暂停,这意味着这个渲染
        // 肯定不会完成。由于我们有新的更新,让我们将其标记为
        // 现在暂停,就在标记传入更新之前。这有
        // 中断当前渲染并切换到更新的效果。
        // TODO: This happens to work when receiving an update during the render
        // phase, because of the trick inside computeExpirationForFiber to
        // subtract 1 from `renderExpirationTime` to move it into a
        // separate bucket. But we should probably model it with an exception,
        // using the same mechanism we use to force hydration of a subtree.
        // TODO: This does not account for low pri updates that were already
        // scheduled before the root started rendering. Need to track the next
        // pending expiration time (perhaps by backtracking the return path) and
        // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
        markRootSuspendedAtTime(root, renderExpirationTime);
      }
    }
    // Mark that the root has a pending update.
    // 为 root 节点调度执行更新。
    markRootUpdatedAtTime(root, expirationTime);
  }

  return root;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  • 这个方法的主要作用是:更新渲染树的 expirationTime,同时找到 root 节点。
  • node.return === null && node.tag === HostRoot 是 root 节点的特征。

# checkForNestedUpdates: 检查嵌套更新

如果 nestedUpdateCount > NESTED_UPDATE_LIMIT 会被判定为嵌套更新然后报 Maximum update depth exceeded 的错误。其中 NESTED_UPDATE_LIMIT 为 50,这是为了防止嵌套更新的死循环。比如在 render 中调用 setState 的情况。

# 嵌套更新的层级数是如何计算的?

let nestedUpdateCount: number = 0;
let rootWithNestedUpdates: FiberRoot | null = null;
1
2

在函数 commitRootImpl() 中有如下代码来更新 nestedUpdateCount :

if (remainingExpirationTime === Sync) {
  // Count the number of times the root synchronously re-renders without
  // finishing. If there are too many, it indicates an infinite update loop.
  if (root === rootWithNestedUpdates) {
	  nestedUpdateCount++;
  } else {
	  nestedUpdateCount = 0;
	  rootWithNestedUpdates = root;
  }
} else {
  nestedUpdateCount = 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

# checkForInterruption

checkForInterruption 方法会标记中断的 fiber,中断的 fiber 标记到 interruptedBy 上。

function checkForInterruption(
    fiberThatReceivedUpdate: Fiber,
    updateExpirationTime: ExpirationTime,
) {
    if (
        enableUserTimingAPI &&
        workInProgressRoot !== null &&
        updateExpirationTime > renderExpirationTime
        // 如果非首次渲染,且更新超时时间超过渲染超时时间则被标记为中断 fiber
    ) {
        interruptedBy = fiberThatReceivedUpdate;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

如果 fiber 的优先级比当前执行的渲染任务的优先级更高,则需要将正在执行的渲染任务终端,转而去执行当前 fiber 的渲染更新任务。

# recordScheduleUpdate:记录调度更新的阶段

recordScheduleUpdate 方法记录调度更新,标记当前调度是处于 commit 阶段还是处于 render 阶段,分别记录在标记 hasScheduledUpdateInCurrentCommit 和 hasScheduledUpdateInCurrentPhase 。

export function recordScheduleUpdate(): void {
    if (enableUserTimingAPI) {
        // isCommitting 标记当前是否处于 commit 阶段
        if (isCommitting) {
            // 标记在当前 commit 有调度更新
            hasScheduledUpdateInCurrentCommit = true;
        }
        if (
            // currentPhase 记录当前的生命周期阶段 
            currentPhase !== null &&
            currentPhase !== 'componentWillMount' &&
            currentPhase !== 'componentWillReceiveProps'
        ) {
            // 标记在当前阶段有调度更新
            hasScheduledUpdateInCurrentPhase = true;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

由代码可以看到,无论是首次渲染还是非首次渲染都会调用 ensureRootIsScheduled 和 schedulePendingInteractions 这两个方法。可见这是两个核心方法。

现在重点看 ensureRootIsScheduled 和 schedulePendingInteractions 方法。

# ensureRootIsScheduled:确保 Root 被调度

// Use this function to schedule a task for a root. There's only one task per
// root; if a task was already scheduled, we'll check to make sure the
// expiration time of the existing task is the same as the expiration time of
// the next level that the root has work on. This function is called on every
// update, and right before exiting a task.
// 这个函数在 root 上调度任务,每个 root 节点只能有一个任务。如果某个 root 已经被调度了任务,则更新该任务的超时时间。
// 这个函数再每次更新和执行任务之前都被调用。
function ensureRootIsScheduled(root: FiberRoot) {
  //lastExpiredTime 初始值为 noWork,只有当任务过期时,会被更改为过期时间(markRootExpiredAtTime方法)
  const lastExpiredTime = root.lastExpiredTime;
  // 正常在拉起调度之前 lastExpiredTime 应该在 noWork,否则就说明应当先把 root 上遗留的更新任务给回调完,这时直接采用了同步的回调。
  if (lastExpiredTime !== NoWork) {
    // 特殊情况:过期的工作应该同步刷新。
    // 同步更新,过期的root立即更新
    root.callbackExpirationTime = Sync;
    root.callbackPriority = ImmediatePriority;
    root.callbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
    return;
  }

  const expirationTime = getNextRootExpirationTimeToWorkOn(root);
  const existingCallbackNode = root.callbackNode;
  // 没有调度任务,做一些重置的工作
  if (expirationTime === NoWork) {
    // There's nothing to work on.
    if (existingCallbackNode !== null) {
      root.callbackNode = null;
      root.callbackExpirationTime = NoWork;
      root.callbackPriority = NoPriority;
    }
    return;
  }

  // TODO: If this is an update, we already read the current time. Pass the
  // time as an argument.
  // 获取当前时间与任务的优先级
  const currentTime = requestCurrentTime();
  const priorityLevel = inferPriorityFromExpirationTime(
    currentTime,
    expirationTime,
  );

  // If there's an existing render task, confirm it has the correct priority and
  // expiration time. Otherwise, we'll cancel it and schedule a new one.
  // 如果存在一个渲染任务,确认它具有正确的优先级和过期时间。 否则,我们将取消它并安排一个新的。
  // 当前的 ROOT 上已经被调度过,这时只需将它更新
  if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    const existingCallbackExpirationTime = root.callbackExpirationTime;
    if ( // 检查是否是合法的渲染任务
      // Callback must have the exact same expiration time.
      existingCallbackExpirationTime === expirationTime &&
      // Callback must have greater or equal priority.
      existingCallbackPriority >= priorityLevel
      // 如果已有的调度优先级更高,这时原来的优先级并不会延迟当前任务的执行,因此可以沿用这个调度,不必将它取消
    ) {
      // Existing callback is sufficient.
      return;
    }
    // Need to schedule a new task.
    // TODO: Instead of scheduling a new task, we should be able to change the
    // priority of the existing one.
    // 否则就取消原来的调度任务
    cancelCallback(existingCallbackNode);
  }

  root.callbackExpirationTime = expirationTime;
  root.callbackPriority = priorityLevel;

  let callbackNode;
  // 同步的渲染任务
  if (expirationTime === Sync) {
    // Sync React callbacks are scheduled on a special internal queue
    // 同步调度由单独的调度队列调度,注意这里并不是 FirstRender
    callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
  } else if (disableSchedulerTimeoutBasedOnReactExpirationTime) {
    callbackNode = scheduleCallback(
      priorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  } else {
    // 异步的渲染任务调度
    callbackNode = scheduleCallback(
      priorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
      // Compute a task timeout based on the expiration time. This also affects
      // ordering because tasks are processed in timeout order.
      {timeout: expirationTimeToMs(expirationTime) - now()},
    );
  }

  root.callbackNode = callbackNode;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  • 同步的调度调用了 scheduleSyncCallback 方法,异步的调度调用了 scheduleCallback 方法。

# scheduleSyncCallback:同步渲染的调度

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];
        // Flush the queue in the next tick, at the earliest.
        // 标记当前正在被同步调度的节点,初始化调度
        immediateQueueCallbackNode = Scheduler_scheduleCallback(
            Scheduler_ImmediatePriority,
            flushSyncCallbackQueueImpl,
        );
    } else {
        // Push onto existing queue. Don't need to schedule a callback because
        // we already scheduled one when we created the queue.
        // 加入调度队列,暂时不被调度,因为在创建syncQueue时就已经初始化过了。
        syncQueue.push(callback);
    }
    return fakeCallbackNode;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 同步任务由特殊的队列 syncQueue 调度,进入队列的首个任务将会初始化调度流程,其他任务只加入队列。
  • 同步任务调度由 Scheduler_scheduleCallback 初始化调度,调度将会在下一个 tick 执行,或者在 flushSyncCallbackQueue 被调用时提前执行。此处正式进入调度器中。
  • fakeCallbackNode 返回一个虚假的 callback 节点,事实上是 {}。

# scheduleCallback:异步渲染的调度

scheduleCallback 内部是调用 Scheduler_scheduleCallback 方法实现的,这个方法接收调度的优先级和 callback,返回一个新的调度任务。

export function scheduleCallback(
  reactPriorityLevel: ReactPriorityLevel,
  callback: SchedulerCallback,
  options: SchedulerCallbackOptions | void | null,
) {
  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
1
2
3
4
5
6
7
8
  • Scheduler_scheduleCallback:生成调度任务,设置回调。这里就进入到调度器中了,我们将在调度器中详解。到这里异步调度就移交给调度器了。

# schedulePendingInteractions 方法:追踪调度过程

此方法内部调用 scheduleInteractions 方法。这个函数的主要作用是管理和更新 pendingInteractionMap 数据结构,其结构 Map(<expirationTime>, <Interactions Set>) ,interactions 是在任务调度的过程中创建的。下面来看看代码:

function scheduleInteractions(root, expirationTime, interactions) {
  // 这个功能由enableSchedulerTracing标志控制,
  if (!enableSchedulerTracing) {
    return;
  }
  // interactions 是挂载在 __interactionsRef.current 上的值。
  // pushInteractions 和 popInteractions 操作 interactions 的值
  if (interactions.size > 0) {
    const pendingInteractionMap = root.pendingInteractionMap;
    const pendingInteractions = pendingInteractionMap.get(expirationTime);
    // 还有 pending 的任务
    if (pendingInteractions != null) {
      interactions.forEach(interaction => {
        if (!pendingInteractions.has(interaction)) {
          // Update the pending async work count for previously unscheduled interaction.
          // 记录 pending 的同步任务的数量
          interaction.__count++;
        }
        // 将 interaction 加入到 pendingInteractions
        pendingInteractions.add(interaction);
      });
    } else {
      // 没有 pending 的任务,则新建一个interactions的集合加入到pendingInteractionMap。
      // pendingInteractionMap 的结构为 Map(<expirationTime>, <Interactions Set>)
      pendingInteractionMap.set(expirationTime, new Set(interactions));

      // Update the pending async work count for the current interactions.
      // 记录当前 pending 的任务数量
      interactions.forEach(interaction => {
        interaction.__count++;
      });
    }
    // 获得订阅者
    const subscriber = __subscriberRef.current;
    if (subscriber !== null) {
      // 获取当前调度的线程 id
      const threadID = computeThreadID(root, expirationTime);
      // 向订阅者发出 onWorkScheduled 的通知
      subscriber.onWorkScheduled(interactions, threadID);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

下面是 pushInteractions 的代码:

function pushInteractions(root) {
  if (enableSchedulerTracing) {
    const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
    __interactionsRef.current = root.memoizedInteractions;
    return prevInteractions;
  }
  return null;
}
1
2
3
4
5
6
7
8

可见 interactions 是从 root.memoizedInteractions 获取的,pushInteractions 返回上一次的 interactions。

上面经过我们的分析,无论是异步回调还是非 FirstRender 的同步回调,都由调度器交接了后续的工作,还剩余一种情况那就 FirstRender 我们还没有分析。我们已经知道 FirstRender 是不会交给调度器去调度的,而是直接由调和器执行了后续的更新回调。在这里我们就来看一下 FirstRender 是怎么处理的,当前我们不会分析的太具体,因为在后续的 更新周期 章节中将会对更新最详细的分析。

# performSyncWorkOnRoot:同步任务调度更新

# performConcurrentWorkOnRoot:异步任务调度更新

# flushSyncCallbackQueue

# 小结

  1. scheduleUpdateOnFiber 的执行原理

scheduleUpdateOnFiber 和 ensureRootIsScheduled 这两个函数结合在一起,大致可以看出 react 调度任务的脉络。在进行进人调度时,会根据当前是同步模式还是异步模式,以及是否是初次渲染采用不同的调度方法,总结如下。

同步 / 异步模式 初次渲染 操作
同步 初次渲染 直接调用 performSyncWorkOnRoot 渲染更新
同步 非初次渲染 调用 scheduleSyncCallback 进行 callback 调度
异步 - 调用 scheduleCallback 进行 callback 调度

由此,我们可以看出:

  • 除了同步渲染且为初次渲染的情况下,才会跳过调度过程,直接进行渲染。否则,都会进行 callback 的调度渲染,调度渲染不会立即执行,而是交给调度器完后后续的回调过程。
  • 同步渲染由 scheduleSyncCallback 调度,异步渲染由 scheduleCallback 调度,最终都是由 unstable_scheduleCallback 来管理调度任务, unstable_scheduleCallback 的功能是创建任务、将任务分为延时任务和即时任务和调度任务的执行。
  1. react 任务调度都做了些什么?
  • react 调度是从 FiberRoot 开始的,FiberRoot 就是没有父节点且标记为 HostRoot 的 Fiber 根节点。
  • 调度开始,需要执行一些检查嵌套更新、检查中断更新、记录调度更新的操作,之后就会根据 expirationTime 来判断当前是什么更新模式,走上面的调度更新流程。
  • 调度更新不仅会影响 Fiber 节点自身,还会可能会影响到 Fiber 的父节点,因为 markUpdateTimeFromFiberToRoot 会更新 fiber 的 expirationTime ,如果 fiber 不是 fiberRoot,还会更新其父节点的 childExpirationTime 。
  • 在调度之后, schedulePendingInteractions 会对调度过程做一些统计、记录工作。
  1. 调和器和调度器的上层接口

在 react-reconciler 包中,文件 SchedulerWithReactIntegration.js 充当了调和器和调度器的桥梁作用,在这个文件中引入了调度器的一些接口并基于调和器进行了封装。从文件名字我们就能看出这一点。

可以看到这些方法实际上是来自于调度器。

const {
  unstable_runWithPriority: Scheduler_runWithPriority,
  unstable_scheduleCallback: Scheduler_scheduleCallback,
  unstable_cancelCallback: Scheduler_cancelCallback,
  unstable_shouldYield: Scheduler_shouldYield,
  unstable_requestPaint: Scheduler_requestPaint,
  unstable_now: Scheduler_now,
  unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
  unstable_ImmediatePriority: Scheduler_ImmediatePriority,
  unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
  unstable_NormalPriority: Scheduler_NormalPriority,
  unstable_LowPriority: Scheduler_LowPriority,
  unstable_IdlePriority: Scheduler_IdlePriority,
} = Scheduler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这些方法是 SchedulerWithReactIntegration 基于调度器的封装。

  • getCurrentPriorityLevel : 从调度器获取当前调度的优先级。
  • scheduleSyncCallback : 通过调度器调度同步任务并维护同步更新列表 syncQueue。
  • cancelCallback : 从调度器中取消当前的回调。
  • flushSyncCallbackQueue : 批量执行 syncQueue 中的同步更新。
编辑 (opens new window)
上次更新: 2022/04/15, 00:23:56
expirationTime与优先级
Lane 与优先级

← expirationTime与优先级 Lane 与优先级→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式