【React原理 - 任务调度和时间分片详解】

概述

在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 PriorityLane PriorityScheduler Priority说明
NoEventPriorityNoLaneNormalSchedulerPriority无优先级
DiscreteEventPrioritySyncLaneImmediateSchedulerPriority离散事件优先级(如点击)
ContinuousEventPriorityInputContinuousLaneUserBlockingSchedulerPriority连续输入事件优先级(如拖拽)
DefaultEventPriorityDefaultLaneNormalSchedulerPriority默认优先级
IdleEventPriorityIdleLaneIdleSchedulerPriority空闲优先级

时间分片

所谓时间分片就是将一个长任务拆分为多个小任务,避免执行长任务导致主线程无法响应而页面卡顿问题。拆分的小任务会在浏览器空闲时执行,并且会定时将控制权还回浏览器,React中默认每个小任务执行时间为5ms,所以在每一帧可能会执行多次,一旦主线程没有其他任务就会执行该小任务(中断/恢复),并不像requestAnimationFrame一样,每一帧只会执行一次。时间分片逻辑主要在shouldYieldToHost/unstable_shouldYield函数中

unstable_shouldYieldshouldYieldToHost是同一个函数,只是导出别名。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函数

状态更新操作通常指setStateforceUpdate这种状态更新重新渲染,并且要知道不是所有的更新都会进入Scheduler调度,通常将可以延迟的长任务、低优先级任务才会进入调度,否则都是同步执行的。长任务startTransitionuseDeferredValue低优先级通常这些任务才会通过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步,我们要知道其中的两个队列:timerQueuetaskQueue。其中两者都是使用小顶堆的结构存储调度任务,因为小顶堆具有父节点值比其子节点值小的特性,再结合优先级值越小优先级越高的特性,所以在该Queue中越前面的任务优先级越高就需要优先执行。

timerQueue

timerQueue保存的就是还未到执行时间的任务,其内部会通过requestHostTimeout调用setTimeout延时调用handleTimeout。主要涉及两个函数handleTimeoutadvanceTimers

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 workif (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;
};

其中主要看三个属性: scheduledHostCallbackhasMoreWorkisMessageLoopRunning

  • scheduledHostCallback就是上面提到的performConcurrentWorkOnRoot回调,该回调在Reconciler中开始进行fiber构造。
  • hasMoreWork表示当前任务是否执行完成,如果被高优先级任务中断则会返回true,在finally中会根据该字段判断是否在继续执行。
  • isMessageLoopRunning最后执行完成之后设置该变量为false,表示当前没有任务执行。

至此我们就介绍完了Scheduler的主要流程。

总结

从上面的介绍我们知道,在React中并不是所有的状态更新都会经过调度,而是将长任务、低优先级、startTransitionuseDeferredValue这种长耗时并且优先级不高的任务通过shouldYieldToHost函数进行时间切片,并更新taskQueue来调度执行任务。

参考文章

万字长文 - 彻底理解react中任务调度和时间分片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/407243.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过C# 读取PDF页面大小、方向、旋转角度

在处理PDF文件时&#xff0c;了解页面的大小、方向和旋转角度等信息对于PDF的显示、打印和布局设计至关重要。本文将介绍如何使用免费.NET 库通过C#来读取PDF页面的这些属性。 文章目录 C# 读取PDF页面大小&#xff08;宽度、高度&#xff09;C# 判断PDF页面方向C# 检测PDF页面…

批发部小程序怎么制作 批发配送系统开发方法

很多领导想要做一个自己公司的批发部小程序系统&#xff0c;但是不知道该怎么做&#xff0c;本次瀚林就为大家详细介绍一下各种批发部小程序系统的开发制作方法为大家做参考。 目前市面上的批发部有很多类型例如常见的&#xff1a;食品、鲜花、零售批发商、冻品、百货、批发城、…

实现BeanPostProcessor

文章目录 1.实现初始化方法1.目录2.InitializingBean.java3.MonsterService.java 实现初始化接口4.SunSpringApplicationContext.java 调用初始化方法5.测试 2.实现后置处理器1.目录2.BeanPostProcessor.java 后置处理器接口3.SunBeanProcessor.java 自定义后置处理器4.SunSpri…

【Python】函数的定义和调用、形参和实参、函数的返回值、多元赋值、全局和局部变量

文章目录 函数的定义函数的调用形参和实参函数的返回值一个 return多个 return多元赋值 变量作用域函数内的变量全局变量和局部变量修改全局变量 函数的定义 函数的定义&#xff1a;分配任务 def 函数名(形参列表):函数体return 返回值def&#xff1a;define&#xff0c;定义…

AI革新下的社交媒体:揭秘Facebook如何利用智能算法

在社交媒体领域&#xff0c;Facebook一直走在技术创新的前沿。随着人工智能&#xff08;AI&#xff09;的飞速发展&#xff0c;Facebook通过智能算法不断革新用户体验、提升平台效率&#xff0c;并推动社交互动的新形式。本文将详细探讨Facebook如何利用AI技术&#xff0c;从个…

ElasticSearch IK分词器的MySQL热部署字典(Docker)

1.下载插件源码 找到自己对应ES版本的下载 Releases infinilabs/analysis-ik GitHub 2.添加mysql驱动依赖 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version><…

C++模拟实现priority_queue(优先级队列)

一、priority_queue的函数接口 从上图我们可以看出&#xff0c; priority_queue也是一个容器适配器&#xff0c;我们使用vector容器来模拟实现priority_queue。 namespace bit{#include<vector>#include<functional>template <class T, class Container vector…

【数据结构】动态顺序表的实现

1.什么是数据结构 数据结构就是把数据元素按照一定的关系组织起来的集合&#xff0c;用来组织和存储数据。通过数据结构&#xff0c;能够有效的将数据组织和管理在一起&#xff0c;按照我们的方式任意对数据进行增删查改等操作。 2.数据结构的分类 数据结构大概可分为逻辑结构…

Selenium + Python 自动化测试19(补充-读取各种文件数据操作)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了数据驱动测试中如何完成重复的测试实例&#xff0c;今天我们补充一些读取各种文件的方法。 本篇文章我们讨论一下如何使用读取txt、CSV、Excel文件&#xff0…

14-17岁未成年如何办理能一直用的手机卡?

14-17岁未成年如何办理能一直用的手机卡&#xff1f; 有些姐妹要去外面上学&#xff0c;都想要一张属于自己的手机卡。 但是因为反诈的原因&#xff0c;对于手机卡的申领特别严格。 很多不满18岁的人能申领的卡&#xff0c;都是物联卡或者纯流量卡&#xff0c;只能上网&#x…

如何评估Redis的性能

如果系统中出现了大 key、热 key 等&#xff0c;往往会导致 Redis 变慢&#xff0c;但是这个慢该如何界定&#xff1f;多久算慢&#xff1f;1秒还是3秒&#xff1f; 这个肯定是没有标准答案&#xff0c;因为这个和你的硬件设备有关。 硬件差一些&#xff0c;平时响应时间都是…

css 宫格样式内容上下结构

结构 <div class"sc-content-group"><div class"sc-content-item"><div class"sc-item-img"><el-image :src"src" :preview-src-list"[src]"></el-image></div><div class"s…

前程无忧搜索接口 JS 逆向:阿里系acw_sc__v2和Sign加密

&#x1f4ca; 前程无忧搜索接口 JS 逆向&#xff1a;阿里系acw_sc__v2和Sign加密 &#x1f50d; 观察网页加密规律&#xff1a;阿里系acw_sc__v2 在分析前程无忧的搜索接口时&#xff0c;我们首先需要关注网页的加密规律。特别是阿里系的 acw_sc__v2 加密机制。这个加密机制通…

图论 最短路

文章目录 单源最短路朴素Dijkstra代码 堆优化Dijkstra代码 Bellman-ford代码 spfaspfa求最短路代码 spfa判断负环代码 多源最短路Floyd代码 单源最短路 朴素Dijkstra 给定一个 n n n 个点 m m m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正…

龙格-库塔法(Matlab实现)

四阶龙格-库塔法介绍 在各种龙格&#xff0d;库塔法当中有一个方法十分常用&#xff0c;以至于经常被称为“RK4”或者就是“龙格&#xff0d;库塔法”。该方法主要是在已知方程导数和初始值时&#xff0c;利用计算机的仿真应用&#xff0c;省去求解微分方程的复杂过程。 令初…

场景库之高精度地图编辑器

一、背景介绍 高精度地图编辑器是场景库生产所需的必要工具&#xff0c;地图编辑器基于JS开发&#xff0c;可对指定的地图进行描绘&#xff0c;生成数字高精度地图。 二、功能介绍 路网元素支持&#xff1a; 类别元素图片交叉口交叉口安全岛交通岛导流岛道路中心圈路口边缘线…

msvcp110.dll丢失修复?教你几招简单易懂的修复msvcp110.dll指南

msvcp110.dll错误通常出现在Windows操作系统中&#xff0c;表明系统缺少或损坏了该msvcp110.dll文件&#xff0c;这是Microsoft Visual C 2012 Redistributable程序包的一部分。下面列出了几种彻底解决此问题的全面方法&#xff0c;以确保解决从简单文件丢失到系统级问题的多种…

使用Intent在活动之间穿梭

文章目录 使用Intent在活动之间穿梭使用显式Intent使用隐式Intent更多隐式Intent的用法 使用Intent在活动之间穿梭 Intent是Android程序中各组件之间进行交互的一种重要方式&#xff0c;它不仅可以指明当前组件想要执行的动作&#xff0c;还可以在不同组件之间传递数据。Inten…

类的构造函数和显式与隐式转化函数

在这个示例中&#xff0c;Iterator类的构造函数是显式的&#xff0c;但通过定义类型转换函数operator Iterator()&#xff0c;你可以通过隐式类型转换来创建Iterator对象。 总之&#xff0c;如果你想要隐式构造一个迭代器对象&#xff0c;你可以将迭代器的构造函数声明为非显式…

Git 的基本使用

1.创建 Git 本地仓库 仓库是进⾏版本控制的⼀个⽂件⽬录。我们要想对⽂件进⾏版本控制&#xff0c;就必须先创建⼀个仓库出来&#xff0c;例如下面代码创建了gitcode_linux的文件夹&#xff0c;之后再对其进行初始化。创建⼀个 Git 本地仓库对应的命令为 git init &#xff0c…