概述
在React15的时候,React使用的是从根节点往下递归
的方式同步创建虚拟Dom,由于递归具有同步不可中断的特性,所以当执行长任务时(通常以60帧为标准,即16.6ms)就会长时间占用主线程长时间无响应,导致页面卡顿,对于交互及其不友好。所以React16中新增了fiber架构和scheduler调度(之前只有渲染器Renderer、协调器Reconciler),在该版本中新增了时间分片逻辑,将一个长任务切分为多个小任务,并在浏览器空闲时,根据优先级执行。其中时间分片的前提就是中断和恢复。本文以React@18.2.0来解释React中的时间分片以及中断和恢复原理。
调度器Scheduler
在了解时间分片之前,先了解下调度器是什么?
所谓Scheduler,如名所示就是用于任务调度,判断在什么时候执行任务避免长时间阻塞主线程的一个工具包。React官方将其设置为独立的包,不仅仅用于React,当其他项目需要对长任务进行优化调度时都可以使用该包,所以其命名为单独的scheduler
,而不是react-scheduler
。
其主要有两个特性:
- 优先级,根据优先级高低决定执行哪个任务,优先执行高优先级任务。
- 时间分片,其内部设置了5ms的执行时间,当任务执行超过设置的时间则会中断执行,并将主线程交回浏览器,避免长时间无响应导致卡顿,在浏览器空闲时再通过恢复来继续执行中断的任务。
针对上面的两个特性,我们来一一解释:
优先级
在React内部优先级有三种:Event优先级
、Lane优先级
、Scheduler优先级
Event优先级:事件优先级,React会将原生事件封装成合成事件时,会根据事件类型携带不同的事件优先级。Event优先级定义时就映射了Lane优先级,即使用Lane优先级来进行的赋值。
// react/packages/react-reconciler/src/ReactEventPriorities.js
export const NoEventPriority: EventPriority = NoLane; // 无优先级
export const DiscreteEventPriority: EventPriority = SyncLane; // 离散优先级
export const ContinuousEventPriority: EventPriority = InputContinuousLane; // 连续输入优先级
export const DefaultEventPriority: EventPriority = DefaultLane; // 默认优先级
export const IdleEventPriority: EventPriority = IdleLane; // 空闲优先级
Lane优先级:车道优先级,位于react-reconciler
协调器中,用于规定更新任务的执行顺序。
为什么是车道优先级呢? 可以看定义该优先级是使用二进制定义的,一方面二进制运算性能是很好的,其次React采用的是
位优先级
,不同的位表示具有该优先级,然后通过位运算能快速获取优先级。然后看后面的二进制定义,是不是像车道的样子,所以命名为车道优先级也表示其是使用二进制定义的。
// react/packages/react-reconciler/src/ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*
Scheduler优先级:调度优先级,位于scheduler
中,用于制定更新任务调度的执行顺序。
// react/packages/scheduler/src/SchedulerPriorities.js
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
// TODO: Use symbols?
export const NoPriority = 0; // 无优先级
export const ImmediatePriority = 1; // 离散事件,点击、keydown、input这种单个事件
export const UserBlockingPriority = 2; // 连续输入优先级,滚动、拖拽等连续事件
export const NormalPriority = 3; // 普通优先级(默认优先级)
export const LowPriority = 4; // 低优先级
export const IdlePriority = 5; // 空闲优先级
并且每种调度优先级都有自己的一个timeout,在unstable_scheduleCallback
创建任务时会根据这个时延来设置expirationTime
,并进一步设置sortIndex
。这里只是介绍下,知道有这个东西,下面会详细说明。
// react/packages/scheduler/src/SchedulerFeatureFlags.js
export const enableSchedulerDebugging = false;
export const enableProfiling = false;
export const frameYieldMs = 5;
export const userBlockingPriorityTimeout = 250;
export const normalPriorityTimeout = 5000;
export const lowPriorityTimeout = 10000;
转换体系:Scheduler是独立的一个包,所以自己内部维护了一个优先级(Scheduler优先级
),所以需要进行优先级转换:Lane优先级 -> Event优先级 -> Scheduler
优先级进行Lane优先级和Scheduler优先级的映射。比如当我们点击按钮触发onclick
事件时,由于合成事件携带了Event优先级,并且该Event优先级是Lane优先级的映射,然后在协调器中创建任务调度时,会根据该Event优先级转换为Scheduler优先级。如下所示:在下面代码中创建调度任务时会执行scheduleTaskForRootDuringMicrotask
函数,并在其中会根据lane优先级获取Event优先级,然后转换为Scheduler优先级
// react/packages/react-reconciler/src/ReactEventPriorities.js
export function lanesToEventPriority(lanes: Lanes): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}
// react/packages/react-reconciler/src/ReactFiberRootScheduler.js
import {
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
cancelCallback as Scheduler_cancelCallback,
scheduleCallback as Scheduler_scheduleCallback,
now,
} from './Scheduler';
function scheduleTaskForRootDuringMicrotask(
root: FiberRoot,
currentTime: number,
): Lane {
// ...
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
// ...
}
从上面的源码来看对应关系如下:
Event Priority | Lane Priority | Scheduler Priority | 说明 |
---|---|---|---|
NoEventPriority | NoLane | NormalSchedulerPriority | 无优先级 |
DiscreteEventPriority | SyncLane | ImmediateSchedulerPriority | 离散事件优先级(如点击) |
ContinuousEventPriority | InputContinuousLane | UserBlockingSchedulerPriority | 连续输入事件优先级(如拖拽) |
DefaultEventPriority | DefaultLane | NormalSchedulerPriority | 默认优先级 |
IdleEventPriority | IdleLane | IdleSchedulerPriority | 空闲优先级 |
时间分片
所谓时间分片就是将一个长任务拆分为多个小任务,避免执行长任务导致主线程无法响应而页面卡顿问题。拆分的小任务会在浏览器空闲时执行,并且会定时将控制权还回浏览器,React中默认每个小任务执行时间为5ms,所以在每一帧可能会执行多次,一旦主线程没有其他任务就会执行该小任务(中断/恢复
),并不像requestAnimationFrame
一样,每一帧只会执行一次。时间分片逻辑主要在shouldYieldToHost/unstable_shouldYield
函数中
unstable_shouldYield
和shouldYieldToHost
是同一个函数,只是导出别名。export {shouldYieldToHost as unstable_shouldYield }
// react/packages/scheduler/src/SchedulerFeatureFlags.js
export const frameYieldMs = 5;
// react/packages/scheduler/src/forks/Scheduler.js
let frameInterval = frameYieldMs;
let startTime = -1;
function shouldYieldToHost(): boolean {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
// The main thread has only been blocked for a really short amount of time;
// smaller than a single frame. Don't yield yet.
return false;
}
// Yield now.
return true;
}
export {
...
shouldYieldToHost as unstable_shouldYield,
...
};
从代码也能看出,该函数shouldYieldToHost
返回一个布尔值,表示当前是否需要将控制权还回浏览器,其中根据已经执行任务时间是否超过设置的定时帧即timeElapsed < frameInterval
来返回控制权,避免长时间阻塞。并且该函数会定时执行,以确保及时返回控制权。其中从下面的React工作循环示意图中,该函数会在其两大循环中都会执行,任务调度循环中调度任务之前以及fiber构造循环之前都会使用该函数进行判断,具体逻辑下面细说。
任务创建和调度
到这里,我们已经对Scheduler的主要特性有所了解了。我们都知道当我们通过setState
触发状态更新后,会发起创建一个更新任务并创建调度任务并等待Scheduler调度,然后在Reconciler中执行fiber构造。所以下面开始介绍Scheduler中的任务创建和调度,进而细说介绍其中的时间分片技术。
先看下网上很火的流程图:
一步一步来,先看任务注册和调度流程。即状态更新 -> dispatchState -> scheduleUpdateOnFiber -> ensureRootIsScheduled -> scheduleCallback -> unstable_scheduleCallback -> requestHostCallback(创建任务、timerQueue、taskQueue) -> schedulePerformWorkUntilDeadline -> performWorkUntilDeadline(通过MessageChannel) -> flushWork -> workLoop -> 后续就是执行调度任务回调,即执行performConcurrentWorkOnRoot函数
。
状态更新操作通常指
setState
、forceUpdate
这种状态更新重新渲染,并且要知道不是所有的更新都会进入Scheduler调度,通常将可以延迟的长任务、低优先级任务才会进入调度,否则都是同步执行的。长任务
、startTransition
、useDeferredValue
、低优先级
通常这些任务才会通过ensureRootIsScheduled
进入scheduleCallback
调度。其中关于这两个React18新加入的api可以查看这篇文章: 【React Hooks原理 - useDeferredValue】、【React Hooks原理 - useTransition】其本质就是降低优先级,以达延迟执行的目的,本文不再赘述。
上面主要列举了从状态更新到更新任务调度执行的整个函数调用,先总体有个眼熟。下面来看代码,并会根据demo结合浏览器bugger来逐一介绍,(其中主要介绍的是重要函数的重要逻辑,不会每行介绍),其中demo运行在react@18.2.0的环境下。
demo示例:
import React, { useState, startTransition } from "react";
function SearchComponent() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
function handleChange(event) {
setQuery(event.target.value);
startTransition(() => {
// 模拟搜索过程
const filteredResults = performSearch(event.target.value);
setResults(filteredResults);
});
}
return (
<div>
<input type="text" value={query} onChange={handleChange} />
<ul>
{results.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
function performSearch(query) {
// 模拟一个复杂的搜索算法
return Array(10000)
.fill(0)
.map((_, index) => `Result ${index} for ${query}`);
}
export default SearchComponent;
1、触发状态更新
通过点击异步按钮,触发setState
进入dispatchState
逻辑。
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
revertLane: NoLane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
const alternate = fiber.alternate;
// 判断当前更新队列中是否有其他更新任务
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher = null;
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
}
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
}
}
该函数主要逻辑如下:
- requestUpdateLane获取本次更新任务的Lane优先级
- 当前没有其他更新任务会进入预计算逻辑,即获取更新后结果
eagerState
然后判断值是否变化,以进行enqueueConcurrentHookUpdateAndEagerlyBailout
跳过本次更新。由于我们示例是在第一个setState断点的,所以第一次会进入到当前逻辑,后面的setState不会进入该逻辑。 enqueueConcurrentHookUpdate
执行该函数将本次更新任务添加到更新队列中,这是React18对于异步批量处理的优化,详细可看这篇文章: 【React Hooks - useState状态批量更新原理】- 然后通过
scheduleUpdateOnFiber
开始进入调度器调度阶段。
2、进入Scheduler调度
在scheduleUpdateOnFiber
中主要就是标记计算优先级、标记更新节点等。其中最重要的是调用ensureRootIsScheduled
开始进入调度。
export function ensureRootIsScheduled(root: FiberRoot): void {
// Schedule a new callback.
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
// 处理同步任务
} else {
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
}
在ensureRootIsScheduled
函数中会通过newCallbackPriority === SyncLane
进行优先级判断,那些任务需要通过Scheduler调度,那些直接同步执行。从上面优先级我们知道对于 长任务、startTransition、useDeferredValue、低优先级通常这些任务才会通过ensureRootIsScheduled进入scheduleCallback调度。在通过scheduleCallback
申请调度之前,会先进行优先级转换即:Lane -> Event -> Scheduler优先级,并将转换后的Scheduler优先级和performConcurrentWorkOnRoot
回调传入调度器中,该回调就是在调度器中频繁出现的callback。
3、创建调度任务
上面两部都是在协调器React-Concilder中进行的,通过scheduleCallback
进入调度器中即Scheduler包的unstable_scheduleCallback
函数。
// packages/scheduler/src/forks/Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
可以将unstable_scheduleCallback
函数拆分为4步:
- 根据传入的可选参数option计算startTime
- 根据Scheduler优先级计算expirationTime,上面说过每个Scheduler优先级都对应了一个timeout时延
- 创建调度任务,newTask
- 根据开始时间和当前时间判断当前调度任务是否需要执行。
其中对于第4步,我们要知道其中的两个队列:timerQueue
、taskQueue
。其中两者都是使用小顶堆的结构存储调度任务,因为小顶堆具有父节点值比其子节点值小的特性
,再结合优先级值越小优先级越高的特性,所以在该Queue中越前面的任务优先级越高就需要优先执行。
timerQueue
timerQueue
保存的就是还未到执行时间的任务,其内部会通过requestHostTimeout
调用setTimeout
延时调用handleTimeout
。主要涉及两个函数handleTimeout
、advanceTimers
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (peek(taskQueue) !== null) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
}
}
handleTimeout
逻辑如下:
- 调用
advanceTimers
函数,根据当前时间和任务开始时间判断,当到执行该任务时,会将其从timerQueue中移到taskQueue进行执行 - 判断taskQueue是否有需要执行的任务,有则调用
requestHostCallback
发起调度,没有则递归调用requestHostTimeout
将timerQueue加入到taskQueue中。
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// Timer was cancelled.
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// Timer fired. Transfer to the task queue.
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer, currentTime);
timer.isQueued = true;
}
} else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}
其中需要注意调度任务的sortIndex属性,由于taskQueue/timerQueue内部使用小顶堆实现,该值表示其所在的位置,优先级越高过期时间越早该值就会越小,该任务所在位置就越在小顶堆上方,就越先被执行。
taskQueue
保存已经到达执行时间的调度任务。当当前没有正在调度的任务时即!isHostCallbackScheduled
,就会执行requestHostCallback
函数调度该任务。
不管有没有到执行时间,最后都是通过
requestHostCallback
函数进行调度。无非当没到时间时,会先保存在timerQueue中等到时间之后在移动到taskQueue中执行
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}
这里无非就是将传入的callback即传入的flushWork
函数绑定给scheduledHostCallback
,然后当前没有任务执行时调用schedulePerformWorkUntilDeadline
flushWork:
function flushWork(hasTimeRemaining, initialTime) {
...
return workLoop(hasTimeRemaining, initialTime);
...
}
flushWork函数主要就是调用了workLoop来进行React两大工作循环的调度循环(While循环)。而workLoop函数可以拆分为三部分来看:
- 调用advanceTimers将到达执行时间的任务移动到taskQueue,等待调度
- 通过While进行调度循环执行回调
- 当然任务是否执行完成,如果执行完成则从timerQueue中获取任务,否则继续执行。
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// This currentTask hasn't expired, and we've reached the deadline.
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
if (enableProfiling) {
markTaskRun(currentTask, currentTime);
}
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// Return whether there's additional work
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
其中第一、三部分比较简单,这里介绍下第二部分:While调度循环
- 首先就是根据时间和shouldYieldToHost判断是否需要跳过当前任务
currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())
- 判断当前callback是否是函数,不是函数表示当前任务执行完成,则将其从taskQueue中去除。如果被高优先级任务中断,则该callback是函数并且不会将该任务从taskQueue中删除,下一次仍然继续执行。
- 执行callback即performConcurrentWorkOnRoot函数,根据返回值是否为function来判断当前任务是否执行完成
这里要知道workLoop返回的布尔值就表示当前任务是否被执行完成,即下面说的hasMoreWork
变量。
schedulePerformWorkUntilDeadline:
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
} else if (typeof MessageChannel !== 'undefined') {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
} else {
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0);
};
}
从上面能知schedulePerformWorkUntilDeadline
就是根据当前不同的环境以不同的方式来执行performWorkUntilDeadline
函数。
- localSetImmediate:在node环境或者低版本IE中通过setImmediate来触发
- MessageChannel:在浏览器端通过MessageChannel触发
- localSetTimeout: 使用setTimeout兜底兼容。
优先使用MessageChannel,是因为setTimeout(fn, 0)也会有4ms的延迟。详细介绍MessageChannel可查看这篇文章: 【React架构 - Scheduler中的MessageChannel】
最终执行performWorkUntilDeadline
函数来执行调度任务
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
startTime = currentTime;
const hasTimeRemaining = true;
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
} finally {
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false;
};
其中主要看三个属性: scheduledHostCallback
、hasMoreWork
、 isMessageLoopRunning
- scheduledHostCallback就是上面提到的performConcurrentWorkOnRoot回调,该回调在Reconciler中开始进行fiber构造。
- hasMoreWork表示当前任务是否执行完成,如果被高优先级任务中断则会返回true,在finally中会根据该字段判断是否在继续执行。
- isMessageLoopRunning最后执行完成之后设置该变量为false,表示当前没有任务执行。
至此我们就介绍完了Scheduler的主要流程。
总结
从上面的介绍我们知道,在React中并不是所有的状态更新都会经过调度,而是将长任务、低优先级、startTransition
、useDeferredValue
这种长耗时并且优先级不高的任务通过shouldYieldToHost
函数进行时间切片,并更新taskQueue来调度执行任务。