React-Reconciler 是用于调度react应用的一个模块,负责把react的调和/渲染阶段使用Scheduler包来调度
React的优先级
react 的优先级是react内部使用的优先级,是独立与Scheduler包中的优先级,在react内部有两个方法用于和Scheduler的优先级置换
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;// NoPriority is the absence of priority. Also React-only.export const NoPriority: ReactPriorityLevel = 90;
// react的优先级转换为Scheduler的优先级function reactPriorityToSchedulerPriority(reactPriorityLevel) {switch (reactPriorityLevel) {case ImmediatePriority:return Scheduler_ImmediatePriority;case UserBlockingPriority:return Scheduler_UserBlockingPriority;case NormalPriority:return Scheduler_NormalPriority;case LowPriority:return Scheduler_LowPriority;case IdlePriority:return Scheduler_IdlePriority;default:invariant(false, 'Unknown priority level.');}}// lane的优先级和scheduler的优先级转换function lanePriorityToSchedulerPriority(lanePriority: LanePriority,): ReactPriorityLevel {switch (lanePriority) {case SyncLanePriority:case SyncBatchedLanePriority:return ImmediateSchedulerPriority;case InputDiscreteHydrationLanePriority:case InputDiscreteLanePriority:case InputContinuousHydrationLanePriority:case InputContinuousLanePriority:return UserBlockingSchedulerPriority;case DefaultHydrationLanePriority:case DefaultLanePriority:case TransitionHydrationPriority:case TransitionPriority:case SelectiveHydrationLanePriority:case RetryLanePriority:return NormalSchedulerPriority;case IdleHydrationLanePriority:case IdleLanePriority:case OffscreenLanePriority:return IdleSchedulerPriority;case NoLanePriority:return NoSchedulerPriority;default:invariant(false,'Invalid update priority: %s. This is a bug in React.',lanePriority,);}}
Lane
在react中存在多种使用不同的优先级
lane 在react可以满足三种需求:
- 可用表示出优先级的不同
- 一个lane只能存在一个更新,但是可以被合并,也就是一个lane可以有批的概念
- 内部一定大量使用了lane,要方便计算
lane采用31位的二进制数表示,位数越小表示优先级更高
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;const NonIdleLanes = /* */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
lane采用了31位的二进制数值表示,那么优先级的相关计算就是使用的是位运算
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {return (a & b) !== NoLanes;}export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {return (set & subset) === subset;}export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {return a | b;}export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {return set & ~subset;}
使用位运算可以很方便的知道一个lane中对于另一个lane的关系
lane中有几个变量同时存在多个1位也就是可以使用多次,也可以包含其他lane。 一个lane被使用时如果有其他的lane包含进来,可以下降的拥有多个位的lane
高优先级打断低优先级任务
优先级高的任务在后产生并开始调度之前会都会通过ensureRootIsScheduled函数来处理
准备本次任务调度协调所需要的lanes和任务优先级,然后判断是否需要调度。 这个函数做的工作:
- 首先获取callbackNode也就是上一次调度的任务
- 检查任务是否过期,将过期任务放入
root.expiredLanes, 这里就是让过期的任务以同步的优先级进行调度 - 获取从root。expiredLanes获取renderLanes, 如果是空说明不需要调度return
- 获取本次任务,也就是新任务的优先级
- 通过判断新任务和旧任务的优先级是否相同,觉得是否发起一次更新
- == 说明没有没有,直接复用旧任务。让旧的任务顺带把新产生的任务做了
- != 说明新任务的优先级一定高于旧任务,在这里把旧的任务取消
- 开启一次新的调度,荣国scheduler包暴漏的schedulerCallback添加一个新的任务
新旧任务的优先级不同时,新任务的优先级一定是高于旧任务的吗? 每次调度去获取任务优先级的时候,都只从
root.pendingLanes中获取优先级未被使用的lane, 如果是空的就获取pendingLanes中最右边也就是优先级最高的lane
比如进入页面时,如果useEffect中对一个state改变。 然后手动调用一个dom事件click。 那么click事件的优先级会大于在useEffect中的优先级
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {// 当前正在被调度的回调const existingCallbackNode = root.callbackNode;// 获取当前应用正在调度的最高优先级的任务lanesconst nextLanes = getNextLanes(root,root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,);// 获取新协调任务的优先级const newCallbackPriority = returnNextLanesPriority();if (nextLanes === NoLanes) {// 在react中,如果没有可用的lane,就直接返回if (existingCallbackNode !== null) {cancelCallback(existingCallbackNode);root.callbackNode = null;root.callbackPriority = NoLanePriority;}return;}// 如果已经存在被调度的任务, 是否打断if (existingCallbackNode !== null) {const existingCallbackPriority = root.callbackPriority;// 优先级相同的情况下不会发起新的调度if (existingCallbackPriority === newCallbackPriority) {return;}// 取消低优先级任务,重新开始调度一次高优先级的任务// 也就是高优先级任务打断低优先级任务,实现基于优先级的任务插队cancelCallback(existingCallbackNode);}// Schedule a new callback.let newCallbackNode;/*在调用函数之前会将react内部的优先级转换为scheduler包中的优先级这里会调用schedulerCallback 发起一次新的调度任务回调在异步模式下是performConcurrentWorkOnRoot,同步为performSyncWorkOnRootschedulerCallback会返回新创建的taskvar newTask = {id: taskIdCounter++, // 任务数callback, // 传入的回调函数,用于本次task的处理程序priorityLevel, // 任务的优先级startTime, // 开始时间expirationTime, // 过期时间sortIndex: -1, // 优先队列的排序依据};*/// 为根节点添加正在调度的任务root.callbackPriority = newCallbackPriority; // 调度任务的优先级属于react内部的root.callbackNode = newCallbackNode; // newTask}
如果一个新的任务的优先级比正在执行的任务优先级高,会取消调上次的任务,重新调度一次新的任务
异步可中断
同步模式为render创建的应用会调用workLoopSync, 异步模式为createRoot 创建的应用调用workLoopConcurrent 二者的区别就是异步可以暂定函数的执行,由shouldYield决定
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent() {
// 异步模式, 可终端
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
// shouldYield 实现
var shouldYieldToHost = function() {
// getCurrentTime 函数为获取当前的performance.now函数返回的时间戳
// daedline 是剩余时间
return getCurrentTime() >= deadline;
};
// 中断任务并不会调用 prepareFreshStack函数重置
中断之后的任务会继续放在taskQueue中
中断可恢复
react Concurrent模式为异步模式,是由createRoot方法创建的根节点应用,ReactDom.render创建的应用为同步模式,二者的区别就是异步模式可被打断
// 在异步模式的的入口调度函数内,在当前正在使用的回调和根节点的回调相同时会返回一个函数
// 由scheduler 托管
function performConcurrentWorkOnRoot(root) {
// ... 略
// callbackNode 为调度任务的callback属性,也就是现在正在处于调度阶段
// originalCallbackNode 是当前正在执行的callback
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
// scheduler包调度开始执行任务的函数
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (
currentTask !== null &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
const callback = currentTask.callback; // 从taskQueue中获取高优先级的任务
if (typeof callback === 'function') {
currentTask.callback = null;
// 在函数内部获取优先级时获取的就是为当前执行回调的优先级,
currentPriorityLevel = currentTask.priorityLevel;
// ... 略
// 执行任务的回调函数
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
// 如果回调函数返回了一个函数,会重新赋值给当前任务的callback属性
// 此时的回调就是performConcurrentWorkOnRoot
currentTask.callback = continuationCallback;
} else {
// 如果不是,并且当前任务执行完毕就从队列中删除
// 或者前后两个任务不同, 比如高优先级任务打断低优先级任务
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
// 如果taskQueue中任务为空,就从timerQueue中获取最新过期的任务,并等待超时时间
if (currentTask !== null) {
return true;
} else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
饥饿问题
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// 检查过期任务,如果存在,立即放到root.expiredLanes,
// 接下来的将这个任务以同步模式立即执行
markStarvedLanesAsExpired(root, currentTime);
// ...
}
已经过期的低优先级任务在下次新任务开始时,会优先被处理
react让低优先级任务过期的方式,变成同步任务解决饥饿问题
