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;
// 获取当前应用正在调度的最高优先级的任务lanes
const 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,同步为performSyncWorkOnRoot
schedulerCallback会返回新创建的task
var 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让低优先级任务过期的方式,变成同步任务解决饥饿问题