过年玩嗨了,有点飘,状态很差。慢慢的从学习中调整过来。
之前整理了react的fiber架构,由于对架构中的优先级概念理解不深,导致对调度过程的一些细节不明其意。So,在下基于源码和自身对理解整理了下面一篇文章,从优先级的角度来看调度过程。轻点喷。
参考源码基于16.8.6
概要:
- 优先级介绍
- 事件优先级与事件派发机制
- 更新优先级
- 任务优先级
- 调度优先级
- more
一、优先级介绍
不同的事件产生不同的更新(lane),更新被收集成任务,任务会分级调度。
react中有四种优先级:
- 事件优先级、
- 更新优先级(lane)、
- 任务优先级、
- 调度优先级(scheulder)
二、事件优先级与事件派发机制
1. 事件优先级
事件优先级分为三类:
- 离散事件(discreteEvent),不连续触发的事件,优先级最低为0。比如click,focus,keydown
- 用户阻塞事件(userBlockingEvent),连续触发的事件,优先级为1。比如scroll,drag,mouseOut
- 连续事件(continousEvent),非触发的连续事件,优先级最高2。比如process、playing、load
参考源码:
packages/react-dom/src/event/DOMEventProperties.js
const discreteEventPairsForSimpleEventPlugin = [
('cancel': DOMEventName), 'cancel',
('click': DOMEventName), 'click',
('close': DOMEventName), 'close',
('contextmenu': DOMEventName), 'contextMenu',
('copy': DOMEventName), 'copy',
('cut': DOMEventName), 'cut',
('auxclick': DOMEventName), 'auxClick',
('dblclick': DOMEventName), 'doubleClick', // Careful!
('dragend': DOMEventName), 'dragEnd',
('dragstart': DOMEventName), 'dragStart',
('drop': DOMEventName), 'drop',
('focusin': DOMEventName), 'focus', // Careful!
('focusout': DOMEventName), 'blur', // Careful!
('input': DOMEventName), 'input',
('invalid': DOMEventName), 'invalid',
('keydown': DOMEventName), 'keyDown',
('keypress': DOMEventName), 'keyPress',
('keyup': DOMEventName), 'keyUp',
('mousedown': DOMEventName), 'mouseDown',
('mouseup': DOMEventName), 'mouseUp',
('paste': DOMEventName), 'paste',
('pause': DOMEventName), 'pause',
('play': DOMEventName), 'play',
('pointercancel': DOMEventName), 'pointerCancel',
('pointerdown': DOMEventName), 'pointerDown',
('pointerup': DOMEventName), 'pointerUp',
('ratechange': DOMEventName), 'rateChange',
('reset': DOMEventName), 'reset',
('seeked': DOMEventName), 'seeked',
('submit': DOMEventName), 'submit',
('touchcancel': DOMEventName), 'touchCancel',
('touchend': DOMEventName), 'touchEnd',
('touchstart': DOMEventName), 'touchStart',
('volumechange': DOMEventName), 'volumeChange',
];
const otherDiscreteEvents: Array<DOMEventName> = [
'change',
'selectionchange',
'textInput',
'compositionstart',
'compositionend',
'compositionupdate',
];
// prettier-ignore
const userBlockingPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
('drag': DOMEventName), 'drag',
('dragenter': DOMEventName), 'dragEnter',
('dragexit': DOMEventName), 'dragExit',
('dragleave': DOMEventName), 'dragLeave',
('dragover': DOMEventName), 'dragOver',
('mousemove': DOMEventName), 'mouseMove',
('mouseout': DOMEventName), 'mouseOut',
('mouseover': DOMEventName), 'mouseOver',
('pointermove': DOMEventName), 'pointerMove',
('pointerout': DOMEventName), 'pointerOut',
('pointerover': DOMEventName), 'pointerOver',
('scroll': DOMEventName), 'scroll',
('toggle': DOMEventName), 'toggle',
('touchmove': DOMEventName), 'touchMove',
('wheel': DOMEventName), 'wheel',
];
const continuousPairsForSimpleEventPlugin: Array<string | DOMEventName> = [
('abort': DOMEventName), 'abort',
(ANIMATION_END: DOMEventName), 'animationEnd',
(ANIMATION_ITERATION: DOMEventName), 'animationIteration',
(ANIMATION_START: DOMEventName), 'animationStart',
('canplay': DOMEventName), 'canPlay',
('canplaythrough': DOMEventName), 'canPlayThrough',
('durationchange': DOMEventName), 'durationChange',
('emptied': DOMEventName), 'emptied',
('encrypted': DOMEventName), 'encrypted',
('ended': DOMEventName), 'ended',
('error': DOMEventName), 'error',
('gotpointercapture': DOMEventName), 'gotPointerCapture',
('load': DOMEventName), 'load',
('loadeddata': DOMEventName), 'loadedData',
('loadedmetadata': DOMEventName), 'loadedMetadata',
('loadstart': DOMEventName), 'loadStart',
('lostpointercapture': DOMEventName), 'lostPointerCapture',
('playing': DOMEventName), 'playing',
('progress': DOMEventName), 'progress',
('seeking': DOMEventName), 'seeking',
('stalled': DOMEventName), 'stalled',
('suspend': DOMEventName), 'suspend',
('timeupdate': DOMEventName), 'timeUpdate',
(TRANSITION_END: DOMEventName), 'transitionEnd',
('waiting': DOMEventName), 'waiting',
];
2. 事件派发机制
- 事件优先级是创建的时候根据事件名字
domEventName确定的 - 根据方法
getEventPriorityForPluginSystem计算出事件优先级之后,则判断调用不同监听事件 - 并使用
runWithPriority将调度事件优先级对应的调度优先级写入Scheduler,供计算更新优先级使用
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据domEventName计算事件优先级
const eventPriority = getEventPriorityForPluginSystem(domEventName);
let listenerWrapper;
// 根据不同的事件优先级调用不同的监听函数
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
packages/scheduler/Scheduler.js
function unstable_runWithPriority(priorityLevel, eventHandler) {
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
}
三、更新优先级
计算更新优先级的大致步骤如下:
-
任务优先级的创建取决与当前的调度优先级(即当前的调度情况)。
-
首先获取当前
Schedule内部的调度优先级Scheduler_getCurrentPriorityLevel,并转换成react内部的调度优先级schedulerPriority,体现在函数getCurrentPriorityLevel中。 -
其次根据得到的
react内部的调度优先级,转换成车道优先级schedulerLanePriority,体现函数在schedulerPriorityToLanePriority。其中值得注意的是针对离散事件的更新任务处理,其特殊处理为InputDiscreteLanePriority -
最后将车道优先级转换成该更新的车道(即更新优先级),在所有的车道中找到空闲的(
~wipLanes)、最高优先级的(lanes & -lanes)车道作为当前更新的优先级。体现函数在findUpdateLane -
言归正传我们着重考察一下lane的计算,体现在
requestUpdateLane(fiber)函数中。
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases,特殊的情况,
// 略。。。
// TODO: Remove this dependency on the Scheduler priority.
// To do that, we're replacing it with an update lane priority.
const schedulerPriority = getCurrentPriorityLevel();
// 使用Schedule的优先级会和react内部的优先级耦合,所以用currentUpdateLanePriority来替代。
// 比如,如果不在Scheduler.runWithPriority,我们获取优先级的时候会拿到正在运行的Scheduler的task的优先级。
let lane;
if (
// 个人理解:离散事件触发的任务特殊处理,返回InputDiscreteLanePriority
// 用户阻塞和连续事件任务则在下面的schedulerPriorityToLanePriority中处理,返回InputContinuousLanePriority
(executionContext & DiscreteEventContext) !== NoContext &&
schedulerPriority === UserBlockingSchedulerPriority
) {
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
const schedulerLanePriority = schedulerPriorityToLanePriority(
schedulerPriority,
);
// ...省略
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
}
- 我们再看看
getCurrentPriorityLevel函数,此函数的作用就是将schedule中优先级转换成react中的优先级
// 源码位置:packages/react-reconciler/src/SchedulerWithReactIntegration.old.js
export function getCurrentPriorityLevel(): ReactPriorityLevel {
switch (Scheduler_getCurrentPriorityLevel()) {
case Scheduler_ImmediatePriority:
return ImmediatePriority;
case Scheduler_UserBlockingPriority:
return UserBlockingPriority;
case Scheduler_NormalPriority:
return NormalPriority;
case Scheduler_LowPriority:
return LowPriority;
case Scheduler_IdlePriority:
return IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}
// schedule中的优先级
export const unstable_ImmediatePriority = 1;
export const unstable_UserBlockingPriority = 2;
export const unstable_NormalPriority = 3;
export const unstable_IdlePriority = 5;
export const unstable_LowPriority = 4;
// react中的优先级
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
- 我们再来看
schedulerPriorityToLanePriority,此函数的作用就是将schedulePriority转换成scheduleLanePriority
export function schedulerPriorityToLanePriority(
schedulerPriorityLevel: ReactPriorityLevel,
): LanePriority {
switch (schedulerPriorityLevel) {
case ImmediateSchedulerPriority:
return SyncLanePriority;
case UserBlockingSchedulerPriority:
return InputContinuousLanePriority;
case NormalSchedulerPriority:
case LowSchedulerPriority:
// TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
return DefaultLanePriority;
case IdleSchedulerPriority:
return IdleLanePriority;
default:
return NoLanePriority;
}
}
- 最后我们看看最重要的
findUpdateLane函数,找出空闲车道(~wipLanes)中最高优先级的车道(lanes & -lanes)
export function findUpdateLane(
lanePriority: LanePriority,
wipLanes: Lanes,
): Lane {
switch (lanePriority) {
case NoLanePriority:
break;
case SyncLanePriority:
return SyncLane;
case SyncBatchedLanePriority:
return SyncBatchedLane;
case InputDiscreteLanePriority: {
const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
if (lane === NoLane) {
return findUpdateLane(InputContinuousLanePriority, wipLanes);
}
return lane;
}
case InputContinuousLanePriority: {
const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
if (lane === NoLane) {
return findUpdateLane(DefaultLanePriority, wipLanes);
}
return lane;
}
case DefaultLanePriority: {
let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
if (lane === NoLane) {
lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
if (lane === NoLane) {
lane = pickArbitraryLane(DefaultLanes);
}
}
return lane;
}
case TransitionPriority: // Should be handled by findTransitionLane instead
case RetryLanePriority: // Should be handled by findRetryLane instead
break;
case IdleLanePriority:
let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
if (lane === NoLane) {
lane = pickArbitraryLane(IdleLanes);
}
return lane;
default:
break;
}
}
更新的本质就是创建一个update对象,并为之计算出一个lane(优先级),并将此update对象以单项链表的结构存入当前
fiber对象的sharedQueue属性中。
四、任务优先级
- 首先进入
scheduleUpdateOnFiber,此函数是所有调度任务的入口。 - 拿到上一步获取的lane,将lane合并至
root.pendingLanes,合并标记整颗fiber树,并设置各个车道的过期时间 - 之后开始计算任务优先级,首先会去处理过期的车道的任务,防止低优先级的任务饿死。
- 计算逻辑主要是从pendingLanes中的车道类型分情况处理(空闲任务、非空闲任务)
- 值得注意的是,当正在渲染时,仅有当前是高优先级的车道任务才能抢占低优先级车道的任务
1. 所有调度的入口函数,所有调度的开始
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
checkForNestedUpdates();
// 将跟新优先级向上合并,直至root
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
// 将更新优先级合并
markRootUpdated(root, lane, eventTime);
// 只考察优先级的代码,所以省略...
}
2. 标记整颗fiber树节点的lane
export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
) {
// 将更新优先级收集
root.pendingLanes |= updateLane;
// 不暂停相同或更低优先级的更新
const higherPriorityLanes = updateLane - 1;
root.suspendedLanes &= higherPriorityLanes;
root.pingedLanes &= higherPriorityLanes;
// 将任务更新开始时间收集
const eventTimes = root.eventTimes;
const index = laneToIndex(updateLane);
eventTimes[index] = eventTime;
}
3. 计算任务优先级
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
const pendingLanes = root.pendingLanes;
let nextLanes = NoLanes;
let nextLanePriority = NoLanePriority;
const expiredLanes = root.expiredLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
// 处理低优先级的任务,防止饿死
if (expiredLanes !== NoLanes) {
nextLanes = expiredLanes;
nextLanePriority = return_highestLanePriority = SyncLanePriority;
} else {
// 计算出下一个任务的车道lane,和车道优先级lanePriority
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
// 非空闲、非阻塞??
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
// 非空闲、暂停的
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
} else {
// 对空闲任务的处理
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
}
}
nextLanes = pendingLanes & getEqualOrHigherPriorityLanes(nextLanes);
// 如果任务正在渲染,高优先级任务将抢占低优先级任务
if (
wipLanes !== NoLanes &&
wipLanes !== nextLanes &&
(wipLanes & suspendedLanes) === NoLanes
) {
getHighestPriorityLanes(wipLanes);
const wipLanePriority = return_highestLanePriority;
if (nextLanePriority <= wipLanePriority) {
return wipLanes;
} else {
return_highestLanePriority = nextLanePriority;
}
}
return nextLanes;
}
5. 任务调度的核心方法
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
const existingCallbackNode = root.callbackNode;
// 收集到期的车道root.expiredLanes |= lane;
markStarvedLanesAsExpired(root, currentTime);
// 计算任务优先级,下一个可以的任务lanes
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 计算新的任务的优先级
const newCallbackPriority = returnNextLanesPriority();
// 复用、取消、新建任务的代码,这里不做展示,只考察上面计算优先级逻辑的代码
// 省略...
// 生成出新任务之后,将任务优先级赋值
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
任务优先级的意义在于比较并处理新、老优先级的更新逻辑。高优先级的新任务抢占低优先级的老任务,相同优先级的新、任务收敛,低优先级的新任务等待高优先级任务。
五、调度优先级
- 调度优先级是
Scheduler内部的优先级 pachages/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;
- 任务的调度根据优先级来分别创建不同延时的任务
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;
// 创建newtask
return newTask;
}