简介
react 中的 fiber 任务的优先级。
lane 与 Scheduler 不同。 互相调用时会转换。
使用了二进制掩码来表达。(根据掩 进行包含,去除等判断,本文不讨论)
文本以 ConcurrentMode 模式为主。
表示优先级
与名字表达的意思相同,赛道。 越靠近右侧,优先级越高。
// 拿出几个举例子
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousLane: Lanes = /* */ 0b0000000000000000000000000000100;
export const DefaultLane: Lanes = /* */ 0b0000000000000000000000000010000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const NonIdleLanes = /* */ 0b0001111111111111111111111111111;
export const IdleLane: Lanes = /* */ 0b0100000000000000000000000000000;
// ... 剩余lane
创建Update结构时,请求一个Lane
触发更新,计算出一个优先级 Lane ,赋值到 update 和 fiber。
拿 setState 流程举例:
// 请求lane, 会计算出相应优先级lane
const lane = requestUpdateLane(fiber);
// 创建update 附加 lane 将链到updateQueue链表尾
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 调度root 其中fiber到root更新lanes
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
fiber向上遍历到fiber root将本次lane合并到lanesupdateQueue将尾部链接update
其中 requestUpdateLane 在找到对应优先级的优先级范围中,优先指定级别较高的位(靠右的),若该固定范围内的lanes位全部被指定,则移动到相邻的较低级别范围的lanes位中指定。
// requestUpdateLane会执行此函数
export function claimNextTransitionLane(): Lane {
const lane = nextTransitionLane;
nextTransitionLane <<= 1;
if ((nextTransitionLane & TransitionLanes) === 0) {
nextTransitionLane = TransitionLane1;
}
return lane;
}
按优先级调度更新
- 更新会执行
scheduleUpdateOnFiber,将本次更新的lane合并到root.pendingLanes。 - 在执行
ensureRootIsScheduled,通过root.pendingLanes找到最高优先级的lane,取名Priority。- **注意!!! 此处批处理。 先看下面,在回来看。 **
- 若当前
root的任务优先级与Priority相同,则直接跳出函数。 公用同一个工作函数root.callbackPriority === Priority
- 即多个
update,公用一个performConcurrentWorkOnRoot。 - 即批处理。
ConcurrentMode模式,通过优先级批处理- 同步模式不按此逻辑,由
batchedUpdates批处理。 (延迟更新)
- 将
Priority转换为Scheduler优先级,让Scheduler进行调度performConcurrentWorkOnRoot。 - 将
Priority保存到root.callbackPriority。 - 调度任务返回的任务节点
taskNode保存到root.callbackNode。
// 简化代码
function scheduleUpdateOnFiber(fiber, lane){
// 从fiber 向上 遍历到 root,将lane合并到沿途fiber.lanes。 还有childLanes同样处理
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
// 将本次 lane 合并到 root.pendingLanes
markRootUpdated(root, lane, eventTime);
// 调度本次 更新
ensureRootIsScheduled(root, eventTime);
// 处理同步模式下的需要 立即更新 的情况
if (
// 同步优先级,NoContext,同步模式
lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode
) {
// 立即执行performSyncWorkOnRoot 执行渲染
flushSyncCallbacksOnlyInLegacyMode();
}
}
function ensureRootIsScheduled(root, currentTime) {
const existingCallbackNode = root.callbackNode;
// 将本次调度时间写入,
// 并检测是否有过期任务, 过期则 写入 root.expirationTimes
// 将root.expiredLanes 加入过期的lane
markStarvedLanesAsExpired(root, currentTime);
// 获取lanes ,可能合并有多个lane
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 取出一个最高优先级
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
// 优先级相同,跳出。
// 公用同一个 工作函数。 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot
// 批处理
if ( existingCallbackPriority === newCallbackPriority ) {
return;
}
if (existingCallbackNode != null) {
// 优先级不同,结束当前任务。
cancelCallback(existingCallbackNode);
}
let newCallbackNode;
// 同步优先级(最高优先级)
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
// 同步模式
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
// ConcurrentMode
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
// 随便 清空所有优先级为立即执行 的任务
if (supportsMicrotasks) {
// 若支持 微任务api 调用 scheduleMicrotask
scheduleMicrotask(flushSyncCallbacks);
} else {
// 不支持 则 Schedule 调度
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else {
// 将lane优先级 转化为 Schedule 优先级
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;
}
// 调度performConcurrentWorkOnRoot
// 将返回的taskNode,保存
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
// 保存优先级,taskNode
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
时间切片调度工作函数,高优先级插队,饥饿问题
可以简单的如下认为: 时间切片:每次宏任务执行 fiber 树一小块,执行时间到就中断,调度下一个宏任务,循环此操作。 高优先级插队:每次任务执行时,判断是否有高优先级任务,有则取消当前任务,执行高优先级任务。 简化代码:
function performConcurrentWorkOnRoot(root, currentTime) {
const originalCallbackNode = root.callbackNode;
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 其中会中断执行, 也会被高优先级插队
// 所以要获取任务的状态
let exitStatus =
// 判断是否有任务过期, 过期则立即同步执行 不在时间切片
shouldTimeSlice(root, lanes)
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
// 因为会中断,所以要判断 fiber树 是否全部执行完。
if (exitStatus !== RootIncomplete) {
// 执行完后的新fiber树
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 将执行 commitRoot
finishConcurrentRender(root, exitStatus, lanes);
}
// 再次调度root,可能有新的高优先级任务插队
ensureRootIsScheduled(root, now());
// 本次任务没执行完,继续调度
if (root.callbackNode === originalCallbackNode) {
// 在Schedule中的callback执行 返回新callback,会改为调度新callback
return performConcurrentWorkOnRoot.bind(null, root);
}
// 也有可能是高优先级任务插队了
// 调度结束
return null;
}
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
// 高优先级插队了
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 抛弃旧堆栈, 构建新堆栈。(将相关变量,相关root属性重置)
prepareFreshStack(root, lanes);
}
workLoopConcurrent();
}
function workLoopConcurrent() {
// 每次执行,!shouldYield() 判断是否还有执行时间
while (workInProgress !== null && !shouldYield()) {
// dfs fiber树,每次workInProgress都是下一个fiber节点
// 执行具体的组件render,创建DOM(非插入)。
performUnitOfWork(workInProgress);
}
}
饥饿问题
当高优先级任务过多,低优先级任务将永远得不到执行,基于此 react 通过设置过期时间来解决。
任务过期了,则立即同步执行。
- 记录时间
- 函数:
ensureRootIsScheduled->markStarvedLanesAsExpired - 将此任务的执行时间计入
root.expirationTimes数组。 - 检测是否有过期任务, 过期则写入
root.expirationTimes,root.expiredLanes合并过期lane。
- 函数:
- 执行过期任务
- 函数:
performConcurrentWorkOnRoot->shouldTimeSlice - 在
shouldTimeSlice中判断root.expiredLanes是否有过期任务,有则不运行时间切片,立即执行同步任务renderRootSync。
- 函数:
如何按优先级更新state?
每个 updateQueue 中的 update 创建时都有 lane 。在计算时,根据当前 renderLanes 只处理优先级足够的 update 。
新的问题:不同优先级的Update互相依赖,如何保证结果正确?
举例:updateQueue: A1 --> B2 --> C1 --> D2 (数字为优先级)。
正常情况下,每个 update 执行后就要被删除,一个 update 更新一次 state ,很合理。
但是,按优先级后,就有问题。
现在,我们假设优先级为 1 , A1 C1 执行后删除,B2 为 (preState) => preState + 1 ,依赖 A1 ,那么我们下次更新就乱了错了。
所以,react 为了保证状态正确。将在第一个优先级不够的 update 保存在 baseUpdate,已算出的值保存在 baseState 。
update更新公式: baseState(上一次state) + baseUpdateQueue + pendingQueueUpdata = newState
hooks更新state简化代码。 class的更新流程差不多。
// 简化的hooks state更新
// useReducer中的代码, useState也是调用的useREducer
const queue = hook.queue;
const current = currentHook
// 被优先级跳过的update
let baseQueue = current.baseQueue;
// 本次render 新的update
const pendingQueue = queue.pending;
// 将baseQueue连接到pendingQueue的链表尾
if (pendingQueue !== null) {
if (baseQueue !== null) {
// 循环链表 queue是尾 next是第一个
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
// 存储baseQueu
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;// 结果 sate
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
// 本次渲染lanes与update lane比较,判断优先级是否足够
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不足
// 克隆 update
const clone = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: null,
};
// 从第一个优先级不足的update 构建newBaseQueueLast
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 当前fiber,将updateLane合并到lanes
// 后续合并到 root.pendingLanes
// 这样 低优先级的update后续会被处理。
// 若不合并lanes,则被跳过的update,可能会一直不被处理
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
} else {
// 优先级狗
if (newBaseQueueLast !== null) {
// 说明 有优先级不足的update了
// 后续的update也要连接到baseQueue的链表尾
const clone = {
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 处理update
// eager 是一个计算的性能优化 具体看hooks
if (update.eagerReducer === reducer) {
newState = update.eagerState;
} else {
// action 就是setSstate(action)的传参
const action = update.action;
// 算出state
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
// 说明所有update 优先级足够
// 则 baseState 和 state 相同
newBaseState = newState;
} else {
// baseQueue 本身是last next是第一个
newBaseQueueLast.next = newBaseQueueFirst;
}
// 新旧state不同,全局变量标记需要更新, 后续工作需要
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// eg:useState的state更新
hook.memoizedState = newState;
// 优先级state
hook.baseState = newBaseState;
// 优先级queue
hook.baseQueue = newBaseQueueLast;
}
总结
- react中工作(遍历fiber,渲染dom),更新
state都和优先级密切相关。 - 优先级由
lane实现 ,调度工作 由Scheduler实现,2者优先级会互相转换。 - 优先级代理的问题,任务饥饿(判断任务是否过期来解决),
state状态一致,由保存旧update链表解决。