React 优先级 lane 与 更新

3,129 阅读4分钟

简介

react 中的 fiber 任务的优先级。 laneScheduler 不同。 互相调用时会转换。 使用了二进制掩码来表达。(根据掩 进行包含,去除等判断,本文不讨论)

文本以 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 ,赋值到 updatefiber。 拿 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 合并到 lanes
  • updateQueue 将尾部链接 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.expirationTimesroot.expiredLanes 合并过期lane
  • 执行过期任务
    • 函数:performConcurrentWorkOnRoot -> shouldTimeSlice
    • shouldTimeSlice 中判断 root.expiredLanes 是否有过期任务,有则不运行时间切片,立即执行同步任务 renderRootSync

如何按优先级更新state?

每个 updateQueue 中的 update 创建时都有 lane 。在计算时,根据当前 renderLanes 只处理优先级足够update

新的问题:不同优先级的Update互相依赖,如何保证结果正确?

举例:updateQueue: A1 --> B2 --> C1 --> D2 (数字为优先级)。

正常情况下,每个 update 执行后就要被删除,一个 update 更新一次 state ,很合理。 ​

但是,按优先级后,就有问题。 ​

现在,我们假设优先级为 1A1 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 链表解决。