「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」
react 版本:v17.0.3
UI产生交互的根本原因是各种事件,这也就意味着事件与更新有着直接的联系。在React中,由于事件有不同的等级,这也就导致了不同事件产生的更新,它们有着不一样的优先级,因此事件的优先级是更新优先级的根源。一个更新的产生可直接导致React生成一个更新任务,最终这个任务被Scheduler调度。
在React中,事件被划分了不同的等级,其最终目的是决定任务调度的轻重缓急,从而实现React的增量渲染,预防掉帧,同时达到页面更顺滑的目的,提升用户体验。因此,React有一套从事件到调度的优先级机制。
事件优先级
事件优先级划分
React按照事件的紧急程度,将它们分成了三个等级:
-
**DiscreteEventPriority:**离散事件,如 click、keydown、focusin 等,这些事件的触发不是连续的,优先级最高
-
**ContinuousEventPriority:**阻塞事件,如 drag、mousemove、scroll 等,这些事件的特点是连续触发,会阻塞渲染,优先级为适中
-
**DefaultEventPriority:**如 load、animation 等事件,优先级最低
事件优先级的具体划分如下:
// packages/react-dom/src/events/ReactDOMEventListener.js
export function getEventPriority(domEventName: DOMEventName): * {
switch (domEventName) {
// Used by SimpleEventPlugin:
case 'cancel':
case 'click':
case 'close':
case 'contextmenu':
case 'copy':
case 'cut':
case 'auxclick':
case 'dblclick':
case 'dragend':
case 'dragstart':
case 'drop':
case 'focusin':
case 'focusout':
case 'input':
case 'invalid':
case 'keydown':
case 'keypress':
case 'keyup':
case 'mousedown':
case 'mouseup':
case 'paste':
case 'pause':
case 'play':
case 'pointercancel':
case 'pointerdown':
case 'pointerup':
case 'ratechange':
case 'reset':
case 'resize':
case 'seeked':
case 'submit':
case 'touchcancel':
case 'touchend':
case 'touchstart':
case 'volumechange':
// Used by polyfills:
// eslint-disable-next-line no-fallthrough
case 'change':
case 'selectionchange':
case 'textInput':
case 'compositionstart':
case 'compositionend':
case 'compositionupdate':
// Only enableCreateEventHandleAPI:
// eslint-disable-next-line no-fallthrough
case 'beforeblur':
case 'afterblur':
// Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case 'beforeinput':
case 'blur':
case 'fullscreenchange':
case 'focus':
case 'hashchange':
case 'popstate':
case 'select':
case 'selectstart':
return DiscreteEventPriority;
case 'drag':
case 'dragenter':
case 'dragexit':
case 'dragleave':
case 'dragover':
case 'mousemove':
case 'mouseout':
case 'mouseover':
case 'pointermove':
case 'pointerout':
case 'pointerover':
case 'scroll':
case 'toggle':
case 'touchmove':
case 'wheel':
// Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case 'mouseenter':
case 'mouseleave':
case 'pointerenter':
case 'pointerleave':
return ContinuousEventPriority;
case 'message': {
// We might be in the Scheduler callback.
// Eventually this mechanism will be replaced by a check
// of the current priority on the native scheduler.
const schedulerPriority = getCurrentSchedulerPriorityLevel();
switch (schedulerPriority) {
case ImmediateSchedulerPriority:
return DiscreteEventPriority;
case UserBlockingSchedulerPriority:
return ContinuousEventPriority;
case NormalSchedulerPriority:
case LowSchedulerPriority:
// TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
return DefaultEventPriority;
case IdleSchedulerPriority:
return IdleEventPriority;
default:
return DefaultEventPriority;
}
}
default:
return DefaultEventPriority;
}
}
事件优先级绑定
在「React 源码解读之合成事件」一文中,详细解析了事件的绑定。在根DOM容器(div#root) 上注册事件时,会根据事件名称,调用getEventPriority函数获取当前事件的优先级,然后根据当前事件的优先级,创建不同优先级的事件监听器listener,最终将该监听器绑定到 root 上去。
事件绑定到 root 上的一个简单流程如下 (具体流程可阅读「React 源码解读之合成事件」) :
1、首先是调用 createEventListenerWrapperWithPriority函数构造事件监听器:
// packages/react-dom/src/events/DOMPluginEventSystem.js
// 1、构造事件监听器
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
2、然后在 createEventListenerWrapperWithPriority 函数中调用getEventPriority函数获取事件的优先级:
// 根据事件名称获取事件的优先级
const eventPriority = getEventPriority(domEventName);
3、接着根据事件优先级创建对应优先级的事件监听器
let listenerWrapper;
// 根据事件优先级返回对应的事件监听函数
switch (eventPriority) {
case DiscreteEventPriority: // 事件优先级最高
listenerWrapper = dispatchDiscreteEvent;
break;
case ContinuousEventPriority: // 事件优先级适中
listenerWrapper = dispatchContinuousEvent;
break;
case DefaultEventPriority: // 事件优先级最低
default:
listenerWrapper = dispatchEvent;
break;
}
4、最后返回当前事件的监听器,并最终调用原生事件监听函数addEventListener将监听器注册到root上:
// 返回当前事件的事件监听函数
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
更新优先级
在「React Hooks 源码解读之 useState」一文中,我们介绍过 hook 对象的创建。它是一个链表,在每个hook上都有一个queue对象,它也是一个链表,存储着当前hook对象的所有update。这些 update,都是通过 createUpdate 构造函数创建的。源码如下:
// packages/react-reconciler/src/ReactUpdateQueue.new.js
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
return update;
}
在 update 对象中,它的 lane 属性代表它的优先级,即更新优先级。
创建update的 4 中情况
在 React 体系中,有 4 种情况会创建 update 对象。
1、应用初始化
React应用的启动模式无论是legacy模式还是 Concurrent模式,都会调用 updateContainer 函数来更新 root。在 updateContainer函数中,会调用 createUpdate函数创建一个 update对象:
// packages/react-reconciler/src/ReactFiberReconciler.new.js
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// 删除了 Dev部分的代码
// container 的current属性保存了更新过程中的一棵fiber树,对应着屏幕上已经渲染好的内容
// 获取更新过程中的 current树
const current = container.current;
// 获取当前时间
const eventTime = requestEventTime();
// 创建一个优先级变量(lane模型,通常称为车道模型)
const lane = requestUpdateLane(current);
// 删除了部分代码
// 删除了 Dev部分的代码
// 新建一个 update
const update = createUpdate(eventTime, lane);
// 删除了部分代码
// 将新建的 update 添加到 update链表中
enqueueUpdate(current, update, lane);
const root = scheduleUpdateOnFiber(current, lane, eventTime);
if (root !== null) {
entangleTransitions(root, current, lane);
}
return lane;
}
2、发起组件更新
当我们在class组件中调用了 setState,本质上调用的是enqueueSetState,在enqueueSetState中,会调用 createUpdate函数创建一个 update对象:
// packages/react-reconciler/src/ReactFiberClassComponent.new.js
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
// 创建update对象
const update = createUpdate(eventTime, lane);
// ...
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
// ...
},
// ...
};
3、context的更新
在更新 context 前,会先从 ContextProvider 类型的节点开始,遍历 fiber 链表,从fiber上的context依赖列表寻找匹配的 consumers,然后调用createUpdate函数创建一个 update对象:
// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberNewContext.new.js#L189
function propagateContextChange_eager<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
// ...
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
// Visit this fiber.
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
while (dependency !== null) {
// Check if the context matches.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
const lane = pickArbitraryLane(renderLanes);
// 调用createUpdate函数创建一个 update对象
const update = createUpdate(NoTimestamp, lane);
update.tag = ForceUpdate;
// ...
}
// ...
}
dependency = dependency.next;
}
}
fiber = nextFiber;
}
}
4、在执行组件生命周期函数时发生错误
在组件的整个生命周期过程中,如果在执行某个生命周期函数时发生了错误,也会调用 createUpdate函数创建一个update对象:
// packages/react-reconciler/src/ReactFiberThrow.new.js
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
// 创建 update 对象
const update = createUpdate(NoTimestamp, lane);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: null};
const error = errorInfo.value;
update.callback = () => {
onUncaughtError(error);
logCapturedError(fiber, errorInfo);
};
return update;
}
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
// 创建 update 对象
const update = createUpdate(NoTimestamp, lane);
update.tag = CaptureUpdate;
// ...
return update;
}
在上述4种情况中,只有「应用初始化」和「发起组件更新」这两种情况创建的update.lane的逻辑是一样的,都是根据当前时间,创建一个update优先级。其余的两种情况,update优先级是从外部传入的。
这里,我们重点关注「应用初始化」和「发起组件更新」这两种情况创建的update.lane的逻辑。在创建 update对象之前,它们都是调用了 requestUpdateLane函数来创建update的优先级。
获取update优先级
// react-reconciler/src/ReactFiberWorkLoop.new.js
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
// fiber 节点上的 mode 属性值来源于 HostFiberRoot 对象的mode
// 标记了属于哪种启动模式(legacy模式、Concurrent模式)
const mode = fiber.mode;
// 启动不是 Concurrent 模式,表示是使用同步优先级做渲染
if ((mode & ConcurrentMode) === NoMode) {
// 启动模式为 legacy 模式
// 返回同步优先级
return (SyncLane: Lane);
} else if (
!deferRenderPhaseUpdateToNextBatch &&
(executionContext & RenderContext) !== NoContext &&
workInProgressRootRenderLanes !== NoLanes
) {
// This is a render phase update. These are not officially supported. The
// old behavior is to give this the same "thread" (lanes) as
// whatever is currently rendering. So if you call `setState` on a component
// that happens later in the same render, it will flush. Ideally, we want to
// remove the special case and treat them as if they came from an
// interleaved event. Regardless, this pattern is not officially supported.
// This behavior is only a fallback. The flag only exists until we can roll
// out the setState warning, since existing code might accidentally rely on
// the current behavior.
// 启动模式是 Concurrent 模式,返回这个正在执行任务的 lane
// 当前新的任务会和现有的任务进行一次批量更新
// pickArbitraryLane 函数返回的lane优先级是高优先级
return pickArbitraryLane(workInProgressRootRenderLanes);
}
// 过渡优先级 分配
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
if (
__DEV__ &&
warnOnSubscriptionInsideStartTransition &&
ReactCurrentBatchConfig._updatedFibers
) {
// 根据过渡优先级往set集合_updatedFibers里添加fiber节点
ReactCurrentBatchConfig._updatedFibers.add(fiber);
}
// The algorithm for assigning an update to a lane should be stable for all
// updates at the same priority within the same event. To do this, the
// inputs to the algorithm must be the same.
//
// The trick we use is to cache the first of each of these inputs within an
// event. Then reset the cached values once we can be sure the event is
// over. Our heuristic for that is whenever we enter a concurrent work loop.
if (currentEventTransitionLane === NoLane) {
// All transitions within the same event are assigned the same lane.
// 通过过渡优先级分配规则分配过渡优先级
currentEventTransitionLane = claimNextTransitionLane();
}
return currentEventTransitionLane;
}
// 更新优先级
// Updates originating inside certain React methods, like flushSync, have
// their priority set by tracking it with a context variable.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
}
// 事件优先级
// This update originated outside React. Ask the host environment for an
// appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
}
requestUpdateLane函数的作用是返回一个合适的update优先级。
1、根据启动模式来决定update的优先级
-
启动模式为legacy模式,那么update的优先级为 SyncLane,即同步优先级。
-
启动模式为Concurrent模式,则调用pickArbitraryLane 函数返回正在执行任务的 lane,是一个高优先级。
const mode = fiber.mode; // 启动不是 Concurrent 模式,表示是使用同步优先级做渲染 if ((mode & ConcurrentMode) === NoMode) { // 启动模式为 legacy 模式 // 返回同步优先级 return (SyncLane: Lane); } else if ( !deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes ) { // 启动模式是 Concurrent 模式,返回这个正在执行任务的 lane // 当前新的任务会和现有的任务进行一次批量更新 // pickArbitraryLane 函数返回的lane优先级是高优先级 //workInProgressRootRenderLanes是一个全局变量,存储着当前工作中的根节点的优先级 return pickArbitraryLane(workInProgressRootRenderLanes); }
2、处于 suspense 过程中,根据过渡优先级分配规则来分配过渡优先级。
分配优先级时,会从过渡优先级的最右边开始分配,后续产生的任务则会依次向左移动一位,直到最后一个位置被分配后,后面的任务会从最右边第一个位置再开始做分配
// 过渡优先级 分配
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
// ...
if (currentEventTransitionLane === NoLane) {
// All transitions within the same event are assigned the same lane.
// 通过过渡优先级分配规则分配过渡优先级
currentEventTransitionLane = claimNextTransitionLane();
}
return currentEventTransitionLane;
}
3、调用getCurrentUpdatePriority来获取当前update任务的优先级:
// 更新优先级
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
}
getCurrentUpdatePriority 源码如下:
// packages/react-reconciler/src/ReactEventPriorities.new.js
let currentUpdatePriority: EventPriority = NoLane;
export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
}
4、调用getCurrentEventPriority,根据当前事件的类型,获取当前事件的优先级:
// 事件优先级
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
在 「应用初始化」和「发起组件更新」这两种情况中,通过requestUpdateLane获取update优先级后,根据当前的update优先级创建一个update对象,最后通过scheduleUpdateOnFiber(current, lane, eventTime)函数,将 update.lane 带入调度阶段。
我们继续跟踪这个update优先级的去向。
update优先级的去向
在scheduleUpdateOnFiber中,update优先级会被 markUpdateLaneFromFiberToRoot 经过二进制运算后添加到fiber节点的 lanes 属性上。
// packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
lane: Lane,
): FiberRoot | null {
// Update the source fiber's lanes
// 更新 lane优先级
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
// ...
// Walk the parent path to the root and update the child lanes.
// 更新父路径上父节点的 childLanes 优先级
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
} else {
/// ...
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
return root;
} else {
return null;
}
}
除了将update优先级添加到fiber节点的lanes属性上,还会通过 markRootUpdated 将update优先级添加到 root 的 pendingLanes 属性上,标记root 上有一个正在等待中的update。
// packages/react-reconciler/src/ReactFiberLane.new.js
export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
) {
root.pendingLanes |= updateLane;
// If there are any suspended transitions, it's possible this new update
// could unblock them. Clear the suspended lanes so that we can try rendering
// them again.
//
// TODO: We really only need to unsuspend only lanes that are in the
// `subtreeLanes` of the updated fiber, or the update lanes of the return
// path. This would exclude suspended updates in an unrelated sibling tree,
// since there's no way for this update to unblock it.
//
// We don't do this if the incoming update is idle, because we never process
// idle updates until after all the regular updates have finished; there's no
// way it could unblock a transition.
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
}
const eventTimes = root.eventTimes;
const index = laneToIndex(updateLane);
// We can always overwrite an existing timestamp because we prefer the most
// recent event, and we assume time is monotonically increasing.
eventTimes[index] = eventTime;
}
在 markRootUpdated 中添加到 root 上的update优先级,将会在任务优先级计算中由它计算出任务优先级。
在scheduleUpdateOnFiber中,update优先级还会同时被添加到全局变量 workInProgressRootUpdatedLanes 上。
if (
deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext
) {
workInProgressRootUpdatedLanes = mergeLanes(
workInProgressRootUpdatedLanes,
lane,
);
}
任务优先级
一个带有lane优先级的update会被一个React的更新任务执行掉,任务优先级用来区分多个更新任务的紧急程度,谁的优先级高,就先处理谁。任务优先级是根据更新优先级计算而来。
我们假设当前产生了一前一后两个update,它们持有各自的更新优先级,也会被各自的更新任务执行。经过优先级计算后,如果后者的任务优先级等于前者的任务优先级,Scheduler 不会取消前者的任务调度,而是复用前者的更新任务,将两个同等优先级的更新合并到一次任务中。如果后者的任务优先级高于前者的任务优先级,那么 Scheduler 就会暂停或取消前者的任务调度,转而调度高优先级的后者。如果后者的的任务优先级低于前者的任务优先级,前者的任务调度也不会被Scheduler取消,而是在前者更新完成后,再次调用 Scheduler 对后者发起一次任务调度。
下面,我们通过代码来解释上面的结论。
1、任务优先级计算
任务优先级在任务即将调度之前去计算,其代码逻辑在ensureRootIsScheduled函数中:
// packages/react-reconciler/src/ReactFiberWorkLoop.new.js
// 使用这个函数为 root 安排任务,每个 root 只有一个任务
// 每次更新时都会调用此函数,并在退出任务之前调用此函数。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
//...
// 确定下一个要工作的lane车道,以及它们的优先级
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
//...
// We use the highest priority lane to represent the priority of the callback.
// 获取最高优先级的lane车道,用来表示 Scheduler.scheduleCallback 返回的节点的优先级
const newCallbackPriority = getHighestPriorityLane(nextLanes);
// ...
}
在 ensureRootIsScheduled 中,调用 getNextLanes 计算出在本次更新中应该处理的这批lanes (nextLanes),从而确定下一个要工作的lane车道及其优先级。
任务优先级计算的原理是这样的:存储在root对象上的lanes (expiredLanes、suspendedLanes、pingedLanes等) 经过 getNextLanes 处理后,挑出那些当前需要紧急处理的 lanes,然后将这些lanes传入 getHighestPriorityLane 中,找出这些lanes的优先级,作为任务优先级。
2、复用现有的更新任务
// 使用这个函数为 root 安排任务,每个 root 只有一个任务
// 每次更新时都会调用此函数,并在退出任务之前调用此函数。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
//...
// Check if there's an existing task. We may be able to reuse it.
// 检查是否存在现有任务。 我们或许可以重用它。
const existingCallbackPriority = root.callbackPriority;
if (
// 现有渲染任务的优先级与下一个渲染任务的优先级相同,
// 则复用现有的渲染任务,将两个同等优先级的更新合并到一次渲染任务中
existingCallbackPriority === newCallbackPriority &&
// Special case related to `act`. If the currently scheduled task is a
// Scheduler task, rather than an `act` task, cancel it and re-scheduled
// on the `act` queue.
!(
__DEV__ &&
ReactCurrentActQueue.current !== null &&
existingCallbackNode !== fakeActCallbackNode
)
) {
// The priority hasn't changed. We can reuse the existing task. Exit.
// 优先级没有改变,则重用现有的任务
return;
}
// ...
}
从上面的代码可以看出,如果前后两个update的优先级是同等的,则直接return,不再执行后续的代码。也就是说,前后两个update的优先级相同时,Scheduler 不会取消前者的任务调度,而是复用前者的更新任务,将两个同等优先级的更新合并到一次任务中。
如果前后两个update的优先级不同,则会执行return后面的代码,发起新的任务调度。
调度优先级
在React体系中,只有并发任务 (concurrent task) 才会经过Scheduler调度。在 Scheduler 中,这个任务会被 unstable_scheduleCallback 进行包装,生成一个属于 Scheduler 自己的 task,这个task持有的优先级就是调度优先级。这个调度优先级由事件优先级和任务优先级计算得出。
调度优先级定义
// 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;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
调度优先级计算
// 使用这个函数为 root 安排任务,每个 root 只有一个任务
// 每次更新时都会调用此函数,并在退出任务之前调用此函数。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
//...
// if 逻辑处理的是同步任务,同步任务不需经过 Scheduler
if (newCallbackPriority === SyncLane) {
// ...
} else {
// else 逻辑处理的是并发任务,并发任务需要经过 Scheduler
// 调度优先级等级
let schedulerPriorityLevel;
// 根据事件优先级决定调度优先级的等级
switch (lanesToEventPriority(nextLanes)) {
// DiscreteEventPriority 离散事件优先级最高,赋予 立即执行任务的优先级(最高)
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
// ContinuousEventPriority:阻塞事件优先级为中优先级,赋予用户阻塞任务的优先级
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
// DefaultEventPriority 优先级,赋予正常的调度优先级
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
// IdleEventPriority 优先级,赋予最低的调度优先级
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
// 默认情况下是正常的调度优先级
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
// 根据调度优先级,调度并发任务
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
//...
}
在上面的代码中(else 语句模块),首先定义了一个调度优先级变量schedulerPriorityLevel,然后把经过getNextLanes计算出来的任务优先级传入 lanesToEventPriority(nextLanes) 中,获取nextLanes的事件优先级,再根据nextLanes的事件优先级赋予相应的调度优先级。
下面我们来看看 lanesToEventPriority 是如何获取到nextLanes所对应的事件优先级的。
// packages/react-reconciler/src/ReactEventPriorities.new.js
export function isHigherEventPriority(
a: EventPriority,
b: EventPriority,
): boolean {
return a !== 0 && a < b;
}
// 任务优先级转换为事件优先级
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;
}
在 lanesToEventPriority 函数中,首先调用 getHighestPriorityLane 函数获取nextLanes中最高优先级的 lane 车道,然后分别调用isHigherEventPriority 和 includesNonIdleWork函数,判断这个最高优先级的lane车道所属的事件优先级。
Scheduler 包装任务
并发任务在经过Scheduler调度时,会被Scheduler包装成属于 Scheduler 自己的 task。这部分的处理逻辑在 Scheduler 的unstable_scheduleCallback函数中。
// packages/scheduler/src/forks/Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {
// 这个 currentTime 获取的是 performance.now() 时间戳
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;
// 属于 Scheduler 自己的 task
var newTask = {
id: taskIdCounter++,
callback, // 这里的 callback 是 performConcurrentWorkOnRoot 函数
priorityLevel, // 调度优先级
startTime, // 任务开始时间
expirationTime, // 任务过期时间
sortIndex: -1, // 用于task的优先级排序
};
if (enableProfiling) {
newTask.isQueued = false;
}
// Scheduler调度任务时传入了 delay time,startTime 是大于 currentTime 的,表示这个任务将会延迟执行
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
// timerQueue 是一个二叉堆结构,以最小堆的形式存储 task
// 向二叉堆中添加节点
push(timerQueue, newTask);
// peek 查看堆的顶点, 也就是优先级最高的`task`
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;
// taskQueue 是一个二叉堆结构,以最小堆的形式存储 task
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 中:
1、首先获取performance.now() 时间戳,计算任务的startTIme。
// 这个 currentTime 获取的是 performance.now() 时间戳
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;
}
2、然后根据调度优先级设置相应的定时时间,由定时时间和startTime计算出任务的过期时间expirationTime。
// 根据调度优先级设置相应的定时时间
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;
3、接着创建一个属于Scheduler自己的task,这个task上的 callback 是 performConcurrentWorkOnRoot 函数。
// 属于 Scheduler 自己的 task
var newTask = {
id: taskIdCounter++,
callback, // 这里的 callback 是 performConcurrentWorkOnRoot 函数
priorityLevel, // 调度优先级
startTime, // 任务开始时间
expirationTime, // 任务过期时间
sortIndex: -1, // 在堆排序时用于比较两个task
};
if (enableProfiling) {
newTask.isQueued = false;
}
4、然后把创建好的 task 存储在最小堆timerQueue或taskQueue中,以便可以在O(1) 的时间复杂度获取到优先级最高的 task。
// Scheduler调度任务时传入了 delay time,startTime 是大于 currentTime 的,表示这个任务将会延迟执行
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
// timerQueue 是一个二叉堆结构,以最小堆的形式存储 task
// 向二叉堆中添加节点
push(timerQueue, newTask);
// peek 查看堆的顶点, 也就是优先级最高的`task`
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;
// taskQueue 是一个二叉堆结构,以最小堆的形式存储 task
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);
}
}
task 的存储与获取解析详见「React 算法应用之堆排序」。
转换关系
事件优先级、更新优先级、任务优先级、调度优先级,它们之间是递进的关系。它们的关系图如下:
1、事件优先级是更新优先级的根源,更新优先级通过调用 getCurrentUpdatePriority 函数,由事件优先级转换而来。
// packages/react-reconciler/src/ReactEventPriorities.new.js
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;
let currentUpdatePriority: EventPriority = NoLane;
export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
}
2、任务优先级用来区分多个更新任务的紧急程度,谁的优先级高,就先处理谁。任务优先级通过调用getHighestPriorityLanes函数,传入更新优先级,由更新优先级计算而来。
// packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
switch (getHighestPriorityLane(lanes)) {
case SyncLane:
return SyncLane;
case InputContinuousHydrationLane:
return InputContinuousHydrationLane;
case InputContinuousLane:
return InputContinuousLane;
case DefaultHydrationLane:
return DefaultHydrationLane;
case DefaultLane:
return DefaultLane;
case TransitionHydrationLane:
return TransitionHydrationLane;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
return lanes & TransitionLanes;
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
case RetryLane5:
return lanes & RetryLanes;
case SelectiveHydrationLane:
return SelectiveHydrationLane;
case IdleHydrationLane:
return IdleHydrationLane;
case IdleLane:
return IdleLane;
case OffscreenLane:
return OffscreenLane;
default:
if (__DEV__) {
console.error(
'Should have found matching lanes. This is a bug in React.',
);
}
// This shouldn't be reachable, but as a fallback, return the entire bitmask.
return lanes;
}
}
3、调度优先级是Scheduler的优先级机制,它是通过任务优先级和事件优先级计算得出。首先由任务优先级计算出事件优先级,然后再由事件优先级计算出调度优先级。
//packages/react-reconciler/src/ReactEventPriorities.new.js
// 参数lanes 是任务优先级
// 任务优先级转换为事件优先级
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;
}
// packages/react-reconciler/src/ReactFiberWorkLoop.new.js
// 调度优先级等级
let schedulerPriorityLevel;
// 根据事件优先级决定调度优先级的等级
// lanesToEventPriority(nextLanes) 返回的是事件优先级
// 这里由事件优先级转换为调度优先级
switch (lanesToEventPriority(nextLanes)) {
// DiscreteEventPriority 离散事件优先级最高,赋予 立即执行任务的优先级(最高)
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
// ContinuousEventPriority:阻塞事件优先级为中优先级,赋予用户阻塞任务的优先级
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
// DefaultEventPriority 优先级,赋予正常的调度优先级
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
// IdleEventPriority 优先级,赋予最低的调度优先级
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
// 默认情况下是正常的调度优先级
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
总结
-
本文介绍了React中的 4 种优先级:事件优先级、更新优先级、任务优先级、调度优先级,它们之间是递进的关系。
-
事件优先级由事件本身决定,按照事件的紧急程度,划分了三个等级:DiscreteEventPriority、ContinuousEventPriority、DefaultEventPriority。
-
更新优先级由事件优先级转换而来,然后将其放到fiber节点的lanes属性和root的pendingLanes属性上。
-
任务优先级由更新优先级计算得来。存储在root对象上的lanes (expiredLanes、suspendedLanes、pingedLanes等) 上。经过 getNextLanes 和 getHighestPriorityLane 处理后,找出这些lanes的优先级,作为任务优先级。
-
调度优先级则是由任务优先级和事件优先级计算得出。首先由任务优先级计算出事件优先级,然后再由事件优先级计算出调度优先级。
-
通过优先级的灵活运用, React实现了**可中断渲染、时间切片(time slicing)、异步渲染(suspense)**等特性。
参考文档: