likes
comments
collection
share

React源码解析-优先级管理

作者站长头像
站长
· 阅读数 73

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顺序决定了优先级的高低。 实际上就是前面所说的赛道位越低优先级越高。

那么问题来了,

  1. 如果同一个setState执行多次会怎么样?
  2. 不同的setState执行又会怎么样?
  3. 低优先级的任务一直靠后,过期了怎么办?
  4. 有高优先级任务插进来怎么办?
  5. 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
评论
请登录