React源码解析-优先级管理
React致力于高效的渲染,主要核心来自于:异步可中断渲染,和时间分片。但这些任务的调度都涉及到优先级的管理。
在react中,大致可分为3种:
- fiber更新优先级 Lane
- task调度优先级 schedule priority
- 更新和调度的优先级转换 priorityLevel
一. Lane
优先级的管理是一个非常复杂的事件,在react16之前,react使用exipreTime来做优先级管理,但多任务重叠,以及多任务分组,优先级比较,expirationTime力不从心,代码也变得臃肿。
react lane pr: github.com/facebook/re…
lane即车道模型,每种类型的值都是二进制数据。先来感受下:
lane数据结构
const TotalLanes = 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;
为什么react使用31位,不是32位二进制数据呢?这是因为最高位表示符号正负,对于赛道模型没有意义。
其中二进制位越低,代表着优先级越高。
源码中,大量使用了二进制位运算,在频繁的计算下,速度更快,内存占用更少。
lane是单个任务的优先级,lanes代表多个任务的优先级。
比如:有一个同步任务,还有个批量更新任务,对于fiber来说就是这样:
0b0000000000000000000000000000001 | 0b0000000000000000000000000000010
那么就占据2个低位赛道。那么lanes = 0b0000000000000000000000000000011
对于一个新的更新或首次渲染时,都将获取一个lane做为更新优先级:requestUpdateLane
requestUpdateLane
function requestUpdateLane(fiber) {
var mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return SyncLane;
} else if ((mode & ConcurrentMode) === NoMode) {
return getCurrentPriorityLevel() === ImmediatePriority$1 ? SyncLane : SyncBatchedLane;
}
// ...
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes)
// ...
}
其中mode有以下枚举:
var NoMode = 0;
var StrictMode = 1;
var BlockingMode = 2;
var ConcurrentMode = 4;
var ProfileMode = 8;
var DebugTracingMode = 16;
这和我们的应用启动模式有关。在react17之前,都是无模式启动,17版本中虽然添加的并发模式,但都是unstable。
如果是无模式或block模式,返回同步lane = 1。
如果是并发模式,需要判断当前的schudle task优先级,如果是最高优先级,返回同步模式1,反之批量同步模式2。
findUpdate 会找到目前最高优先级的任务去执行,那么如何找到最高优先级的呢?
switch (lanePriority) {
case NoLanePriority:
// ...
case SyncLanePriority:
// ...
// ...
}
其实就是固定的case顺序决定了优先级的高低。 实际上就是前面所说的赛道位越低优先级越高。
那么问题来了,
- 如果同一个setState执行多次会怎么样?
- 不同的setState执行又会怎么样?
- 低优先级的任务一直靠后,过期了怎么办?
- 有高优先级任务插进来怎么办?
- lane的优先级会影响schedule优先级吗?
markStarvedLanesAsExpired
function markStarvedLanesAsExpired(root, currentTime) {
// ...
while (lanes > 0) {
var index = pickArbitraryLaneIndex(lanes);
var lane = 1 << index;
var expirationTime = expirationTimes[index];
if (expirationTime === NoTimestamp) {
if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) {
expirationTimes[index] = computeExpirationTime(lane, currentTime);
}
} else if (expirationTime <= currentTime) {
root.expiredLanes |= lane;
}
lanes &= ~lane;
}
}
给每个lanes的赛道都设置过期时间,如果存在过期的lane,都将归结到expiredLanes上。
computeExpirationTime
function computeExpirationTime(lane, currentTime) {
getHighestPriorityLanes(lane);
var priority = return_highestLanePriority;
if (priority >= InputContinuousLanePriority) {
return currentTime + 250;
}else if (priority >= TransitionPriority) {
return currentTime + 5000;
} else {
return NoTimestamp;
}
}
return_highestLanePriority是当前任务中最高优先级的lane,如果是用户时间,过期是250ms,如果是transition,则5s。反之为-1。
expirationTimes是一个31位的数组,存放着每个赛道的过期时间。
getNextLanes
function getNextLanes(root, wipLanes) {
var pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return_highestLanePriority = NoLanePriority;
return NoLanes;
}
if (expiredLanes !== NoLanes) {
nextLanes = expiredLanes;
nextLanePriority = return_highestLanePriority = SyncLanePriority;
} else {
var nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
var nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes; // 去除挂起的任务即异步lane
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
var nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
} else {
var unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
nextLanePriority = return_highestLanePriority;
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
nextLanePriority = return_highestLanePriority;
}
}
}
}
// ...
}
pendingLanes是需要处理的lane集合。如果为空,也意味着没有优先级的任务需要处理了。
expiredLanes是存放过期的lane,也意味着饥饿问题在这里得到解决,过期了任务直接执行做为同步任务最高优先级。
如果是正常任务,那么走getHighestPriorityLanes获取所有任务中最高优先级任务做,反之存在挂起任务,比如suspense,与即将进行的lanes取交集,从交集中取出最高优先级。
如果以上都不满足,说明进入不重要或闲置的任务处理。同理,在这些不重要的任务中获取最高优先级处理。
callbackPriority
if (existingCallbackNode !== null) {
var existingCallbackPriority = root.callbackPriority
if (existingCallbackPriority === newCallbackPriority) {
return;
}
cancelCallback(existingCallbackNode);
}
这里,很简单了,如果之前存在相同的优先级的任务,后续的任务将被终止。 其实也意味着,多次相同的setState只会执行一次。那么问题来了,从业务代码来看,需要更新最后一次state值呀,而这里只接受了第一次更新请求,后面被return了,react又是怎么做到更新正确的呢?
这个问题,待看我的另一篇文章:setState源码解析。本质上setState更新请求,和fiber更新完全是两码事,数据的更新不是强相关。
能进入cancelCallback这一步,说明优先级不低,并且相同的优先级也被过滤了。说明有个高优先级任务进来,那么这里取消当前的callbackNode,进而继续往下进行高优先任务调度。
newCallbackNode
var newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));
} else {
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
}
代码很明显了,setState究竟是异步还是同步,其实答案都不对。setState可以是同步可以是异步。这待看项目的启动模式。
scheduleSyncCallback是维护者更新队列,批量同步执行performSyncWorkOnRoot。 而后两者,需要进入schedule阶段,可以参考之前的文章关于schdule的解析。而这种情况下就是异步完成,但记住,不一定是在一次事件循环中完成,可能需要多次事件循环。
二. schedulePriority
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;
}
schedule调度的优先级,最终体现在timeout上,即每个task的expirationTime。schedule内部调度时,如果过期的任务会立刻执行,反之通过时间分片执行。
ImmediatePriority是最高优先级,UserBlockingPriority是250ms以内执行。
IdlePriority闲置任务在很久内挂起,不着急执行。LowPriority低优先在10s内。
NormalPriority,正常优先级在5s内。
schedule本身的优先级和lane没有必然关系。是各自的优先级体系。但lane和schedule优先级如何转化呢?
三. lanePriorityToSchedulerPriority
function lanePriorityToSchedulerPriority(lanePriority) {
switch (lanePriority) {
case SyncLanePriority:
case SyncBatchedLanePriority:
return ImmediatePriority;
case InputDiscreteHydrationLanePriority:
case InputDiscreteLanePriority:
case InputContinuousHydrationLanePriority:
case InputContinuousLanePriority:
return UserBlockingPriority;
case DefaultHydrationLanePriority:
case DefaultLanePriority:
case TransitionHydrationPriority:
case TransitionPriority:
case SelectiveHydrationLanePriority:
case RetryLanePriority:
return NormalPriority;
case IdleHydrationLanePriority:
case IdleLanePriority:
case OffscreenLanePriority:
return IdlePriority;
case NoLanePriority:
return NoPriority;
default:
{
{
throw Error( "Invalid update priority: " + lanePriority + ". This is a bug in React." );
}
}
}
}
同步任务为最高优先级调度,用户事件为第二优先级。其余依次排列。
reactPriorityToSchedulerPriority
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
switch (reactPriorityLevel) {
case ImmediatePriority$1:
return Scheduler_ImmediatePriority;
case UserBlockingPriority$2:
return Scheduler_UserBlockingPriority;
case NormalPriority$1:
return Scheduler_NormalPriority;
case LowPriority$1:
return Scheduler_LowPriority;
case IdlePriority$1:
return Scheduler_IdlePriority;
default:
{
{
throw Error( "Unknown priority level." );
}
}
}
}
react内部优先级与schedule转化关系。 本质上,就是一个个map。
四. 总结
lane就像赛道一样,每个赛道都有自己的优先级,越低位优先级越高。同一时间可能有许多赛道任务,lane是解决任务处理的优先级。更新请求的最终落实是需要schedule调度执行。schedule本身也是异步执行的,来模拟浏览器空闲事件requestIdeCallback api。它并不是react专用,任何库/框架都可以使用schedule。
转载自:https://juejin.cn/post/7200241104666034235