React 源码揭秘 | lane模型

React@17 引入了lane 车道 模型来代替之前的expirationTime过期时间的方式来管理调度优先级。

早期版本的React,每次更新都会创建一个Update对象,这个对象包含一个过期时间 expirationTime

过期时间越小,优先级越高,和我们前面说的scheduler库处理逻辑是一样的。

这样会带来一些问题

1. expirationTime无法很好的表示批次的概念,判断某些更新Update是否处于一个批次的判断方式是,判断当前Update的过期时间是否在当前批次处理时间范围之内,这样带来的问题是,批次必须是连续的, 1 2 3 4 5 我不能吧 23 4 单独分成一批进行处理 

2. 由于update的执行必须按照过期时间的顺序来,比如 2 3 不执行无法执行 4 ,这样就导致,如果2/3是个IO阻塞任务,比如Suspense,就会造成4的阻塞  4可能是个CPU密集任务,但是在等待的过程中,造成CPU资源浪费.

什么是Lane

lane即车道的意思,其本质是个二进制的数字类型,其定义如下: (fiberLanes.ts)

/** 单车道 */
export type Lane = number;
/** 多车道 (优先级合集) */
export type Lanes = number;

export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000100000;

export const SyncUpdateLanes: Lane =
  SyncLane | InputContinuousLane | DefaultLane;

export const TransitionLane: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111110000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;

const NonIdleLanes: Lanes = /*                          */ 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0010000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b0100000000000000000000000000000;
export const DeferredLane: Lane = /*                    */ 0b1000000000000000000000000000000;

可以看到,单个lane只能有一位是1,其余都是0,每个lane优先级1的位置都不同,互相没有重叠,数字越小,即1越靠前,优先级越高。

多个lane的或运算代表批的概念 即lanes,如

const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111110000000;

表示所有的Transition优先级所占用的lane的合集

优先级从上到下 依次是同步优先级 

SyncLane 同步优先级

InputContinuousLane 连续输入事件优先级

DefaultLane 默认优先级

TransitionLane 过度优先级

IdleLane 等待优先级 等等

Lane的基本运算

合并Lane

使用按位或的方式即可 

/**
 * 合并两个优先级
 * @param lane1
 * @param lane2
 * @returns mergedLanes
 */
export function mergeLane(lane1: Lane, lane2: Lane): Lanes {
  return lane1 | lane2;
}
从lanes中移除某个lane

使用lanes & ~lane 的方式,如下:

/**
 * 从某个lanes集合中 移除单个lane/lanes
 * @param laneSet
 * @param subset
 * @returns
 */
export function removeLanes(laneSet: Lanes, subset: Lane | Lanes): Lanes {
  return laneSet & ~subset;
}
获取最高优先级lane

注意,lane模型中,数字越小,优先级越高,基于这个设定,可以通过 lanes&-lanes 获取最高优先级的lane

/**
 * 从某个lanes集合中,找到优先级最高的lane
 * 注意,lane模型中,越小优先级越高,计算对应如下
 * ( min(lanes) = lanes&-lanes 注意-lanes 是二进制的取反+1)
 *  举例: 0b00110 => 取反 0b11001 => 加1 => 0b11010 => 0b00110 & 0b11010 = 0b00010
 * @param lanes
 * @returns
 */
export function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes & -lanes;
}
判断lanes集合是不是另外一个lanes集合的子集

使用 lanes & subLanes === subLanes 来判断,如下:

/**
 * 判断某个lane集合是不是另外一个lanes集合的子集 sub必须完全包含在
 * @param laneSet
 * @param subSet
 */
export function isSubsetOfLanes(laneSet: Lanes, subSet: Lanes | Lane) {
  return (laneSet & subSet) === subSet;
}
判断一个lanes集合是否和另一个lanes有交集

使用 lanes1 & lane2 === NoLane 

/**
 * 某个set集合 是否包含某个集合的部分lane 包含即可
 * @param laneSet
 * @param subSet
 * @returns
 */
export function includeSomeLanes(laneSet: Lanes, subSet: Lanes | Lanes) {
  return (laneSet & subSet) !== NoLanes;
}

使用如上运算,我们就可以对优先级进行合并,移除,或者是判断一批更新中是否包含了某些更新。

lane结合scheduler

我们知道,scheduler单独发包,采用expirationTime的方式,处理优先级。

而react中,引用了scheduler,如果优先级是 SyncLane 则把更新任务放到微任务队列执行,如果低于SyncLane 则放到scheduler中转换成expirationTime运行。

react中,实现了lanes和cheduler优先级互相转换的函数:

/**
 * lanes优先级转scheduler优先级 (取lanes中优先级最高的lane 调用getHighestPriorityLane)
 * @param lanes
 */
export function lanesToSchedulerPriority(lanes: Lanes): PriorityLevel {
  const highestPriorityLane = getHighestPriorityLane(lanes);
  switch (highestPriorityLane) {
    case SyncLane:
      return PriorityLevel.IMMEDIATE_PRIORITY;
    case InputContinuousLane:
      return PriorityLevel.USER_BLOCKING_PRIORITY;
    case TransitionLane:
      return PriorityLevel.LOW_PRIORITY;
    default:
      return PriorityLevel.NORMAL_PRIORITY;
  }
}

/** 转换函数 */
/**
 * scheduler 优先级 转 lane优先级
 * @param schdulerPriority
 */
export function schedulerPriorityToLane(schdulerPriority: PriorityLevel): Lane {
  switch (schdulerPriority) {
    case PriorityLevel.IMMEDIATE_PRIORITY:
      return SyncLane;
    case PriorityLevel.USER_BLOCKING_PRIORITY:
      return InputContinuousLane;
    case PriorityLevel.NORMAL_PRIORITY:
      return DefaultLane;
    default:
      return IdleLane;
  }
}

如何获得优先级lane

当react中触发一个更新时,需要给当前update分配一个优先级lane,其实现方法就是

requestUpdateLane 实现如下:

/**
 * 根据当前update触发上下文 获取update优先级
 * 比如 setState在effect中触发和在onclick中触发 有不一样的优先级
 */
export function requestUpdateLane(): Lane {
  if (isTransition) {
    return TransitionLane
  }
  const currentUpdateLane = schedulerPriorityToLane(
    scheduler.getCurrentPriorityLevel()
  );
  return currentUpdateLane;
}

这里需要详细说一下,React通过scheduler.runWithPriority 和 getcurrentPriority完成和scheduler之间的优先级获取和设置。

React源码揭秘 | scheduler 并发更新原理_react并发更新如何实现任务切片的-优快云博客

这篇文章详细讲了scheduler的工作原理,这里不赘述。

在updateContainer时,创建update并且入队,这部分的逻辑被放到了scheduler.runWithPrioriy中运行

const updateContainer = (element: ReactElement, root: FiberRootNode) => {
  // 默认情况下 同步渲染
  scheduler.runWithPriority(PriorityLevel.IMMEDIATE_PRIORITY, () => {
    // 请求获得当前更新lane
    const lane = requestUpdateLane();
    // 获hostRootFiber
    const hostRootFiber = root.current;
    // 更新的Element元素入队
    hostRootFiber.updateQueue?.enqueue(
      new Update<ReactElement>(element, lane),
      hostRootFiber,
      lane
    );
    // scheduleUpdateOnFiber 调度更新
    scheduleUpdateOnFiber(root.current, lane);
  });
};

runWithPriority会立刻运行传入的函数,并且在上下文把全局优先级设置为传入的优先级。

在传入的函数中,通过requestUpdateLane获取优先级,其实就是调用getCurrentPriority

这个函数会把runQWithPriority设置的全局优先级返回,再通过schedulerPriorityToLane转换成lane优先级,即SyncLane.

可以看到,react默认情况下,都是以Sync同步的方式运行更新的

函数执行之后,runWithPriority会讲全局优先级改回之前的优先级(默认的 NORMAL_PRIORITY)

runWithPriority实现如下:

  /** 以某优先级同步运行 */
  runWithPriority(priorityLevel: PriorityLevel, callback: any) {
    const priviouseLevel = this.currentPriorityLevel
    this.currentPriorityLevel = priorityLevel
    try {
      callback()
    } catch (e) {
      console.warn(e)
    } finally {
      this.currentPriorityLevel = priviouseLevel
    }
  }

其流程如图所示:

合成事件中优先级处理

对于不同的事件有不同的优先级 比如用户点击事件 用户拖拽事件 等等

react实现了一个函数,用来把事件类型转换成scheduler优先级 如下:

/** 事件转优先级 */
export function eventTypeToSchedulerPriority(eventType: string) {
  switch (eventType) {
    case "click":
    case "keydown":
    case "keyup":
    case "keydown":
    case "keypress":
    case "keyup":
    case "focusin":
    case "focusout":
      return PriorityLevel.IMMEDIATE_PRIORITY;
    case "scroll":
    case "resize":
    case "mousemove":
    case "mouseenter":
    case "mouseleave":
    case "touchstart":
    case "touchmove":
    case "touchend":
      return PriorityLevel.USER_BLOCKING_PRIORITY;

    case "input":
    case "change":
    case "submit":
    case "focus":
    case "blur":
    case "select":
    case "drag":
    case "drop":
    case "pause":
    case "play":
    case "waiting":
    case "ended":
    case "canplay":
    case "canplaythrough":
      return PriorityLevel.NORMAL_PRIORITY;

    case "abort":
    case "load":
    case "loadeddata":
    case "loadedmetadata":
    case "error":
    case "durationchange":
      return PriorityLevel.LOW_PRIORITY;

    default:
      return PriorityLevel.IDLE_PRIORITY;
  }
}

在合成事件触发中,会调用这个函数,获取当前合成事件的scheduler优先级,并且调用runWithPriority 如下:

/* 执行事件 */
function triggerEventListeners(
  listeners: SyntheticEventListener[],
  event: Event
) {
  listeners.forEach((listener) =>
    scheduler.runWithPriority(eventTypeToSchedulerPriority(event.type), () => {
      listener(event);
    })
  );
}

当我们在不同的合成事件中触发更新时,比如setState 或者 hooks的setter函数,其内部就会创建一个更新Update,并且调用 requestUpdateLane获得当前scheduler的全局优先级,并且转换成lane。通过这种方式,在不同优先级的上下文中触发更新,都会获得当前上下文对应的优先级。

优先级调度&批处理

我们知道,React17版本之前,setState是异步的,也就是说你无法在设置完state之后立刻获得最新的状态值,然而你如果把setState放到setTimeout中脱离react的调度,则就是同步运行了。

在新版本的React中,由于lanes机制的引入,任何地方的setState都是异步运行了,为什么?

早起React采用类scheduler的expirationTime过期时间来完成优先级调度,这就导致一次批量更新必须更新过期时间连续的Update,React中包含一个变量来记录当前批处理的范围,也就是判读当前更新的过期时间是否在当前批处理的范围内。

这就导致,对于在批处理范围内的更新,React会调度异步处理,但是对于setttimeout内部的setState,由于其执行在同步代码之后,当运行的时候批处理过程已经结束了,在老版本React的ensureRootIsScheduled中会检查,如果当前更新已经在批处理之后了,就会同步的调用处理。

新版本的React由于采用lane模型,这就使不论settimeout还是同步代码中的setState都统一的根据lane处理,同一批次只处理同一个lane的任务,这就和过期时间无关了,不论setState在什么时候执行,React在一次更新中只会处理当前更新lane对应的Uodate。

在详细介绍更新流程之前,我们先补充一些变量和属性

wipRenderLane - 当前workLoop正在处理的lane

在workLoop.ts中,定义一个全局变量wipRenderLane: Lane = NoLane 

/** 表示当前正在render阶段对应的任务对应的lane 用来在任务中断后重启判断跳过初始化流程 */
let wipRootRenderLane: Lane = NoLane;

其是一个lane类型,表示当前更新所对应的处理优先级Lane

FiberRootNode.pendingLanes & finishedLane

在全局root节点上,定义

1. pendingLanes,表示当前react等待处理的lanes 

对应两个操作函数: 

/**
 * 把某个更新的lane加入到root.pendingLanes
 * @param root
 * @param lane
 */
export function markRootUpdated(root: FiberRootNode, lane: Lane) {
  root.pendingLanes = mergeLane(root.pendingLanes, lane);
}

/**
 * 去掉root的某条lane (标记lane对应任务执行完成)
 * @param root
 * @param lane
 */
export function markRootFinished(root: FiberRootNode, lane: Lane) {
  root.pendingLanes = removeLanes(root.pendingLanes, lane);
}

2. finishedLane 表示在render阶段处理完的lane,给commit阶段用 类似于finishedWork

lane冒泡

当一个更新触发时,其内部在把更新enqueue进updateQueue之后,会调用scheduleUpdateOnFib

er函数来触发更新渲染

/** 派发修改state */
function dispatchSetState<State>(
  fiber: FiberNode,
  updateQueue: UpdateQueue<State>,
  action: Action<State>
) {
  // 获取一个优先级 根据 dispatchSetState 执行所在的上下文
  const lane = requestUpdateLane();
  // 创建一个update对象
  const update = new Update(action, lane);
    ...... ... ...
  // 入队 并且加入到fiber上
  updateQueue.enqueue(update, fiber, lane);
  // 开启调度时,也需要传入当前优先级
  scheduleUpdateOnFiber(fiber, lane);
}

在scheduleUpdateOnFiber中,会先调用markUpdateFromFiberToRoot,将当前更新的lane,冒泡到其祖先节点的childLanes上,并且调用markRootUpdate把当前更新lane合并加入到root.pendingLanes上,实现如下:

/** 在Fiber中调度更新 */
export function scheduleUpdateOnFiber(fiberNode: FiberNode, lane: Lane) {
  /** 先从更新的fiber节点递归到hostRootFiber
   *  这个过程中,一个目的是寻找fiberRootNode节点
   *  一个是更新沿途的 childLines
   */
  const fiberRootNode = markUpdateLaneFromFiberToRoot(fiberNode, lane);
  // 更新root的pendingLane, 更新root节点的pendingLanes 表示当前正在处理的lanes
  markRootUpdated(fiberRootNode, lane);
  // 保证根节点被正确调度
  ensureRootIsScheduled(fiberRootNode);
}

ensureRootIsSchuduled函数用来调度更新任务,其实现如下:

export function ensureRootIsScheduled(root: FiberRootNode) {
  // 先实现同步调度 获取当前最高优先级
  const highestPriorityLane = getNextLane(root);
  // 判断,如果不存在优先级 说明没有任务需要继续调度了 直接returna
  if (highestPriorityLane === NoLane) return;
  // 批处理更新, 微任务调用更新
  if (highestPriorityLane === SyncLane) {
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    // 设置微任务回调 冲洗缓冲区
    flushSyncCallbacks();
  } else {
    // 其他优先级 使用scheduler调度
    scheduler.scheduleCallback(
      lanesToSchedulerPriority(highestPriorityLane),
      performConcurrentWorkOnRoot.bind(null, root)
    );
  }
}

可以看到,此函数先从root.pendingLanes上获取一个优先级最高的lane,即getNextLane, 其内部就是对pendingLanes调用getHighestPriorityLane函数

/**
 * 获取当前root优先级最高的lan
 * @param lanes
 */
export function getNextLane(root: FiberRootNode): Lane {
  const pendingLanes = root.pendingLanes;
  /** 调用getHighestPriorityLane 获取最高优先级lane */
  return getHighestPriorityLane(pendingLanes);
}

获取优先级之后检查,如果是NoLane  表示当前已经不存在更新了,直接return

如果是同步优先级 SyncLane

则需要同步处理更新,需要将更新逻辑放到微任务执行,实现如下:

/** 同步任务更新队列 */
type SyncCallback = (...args: any[]) => void;

/** 存储callback数组 */
let syncTaskQueue: SyncCallback[] = [];

/** 入队 */
export function scheduleSyncCallback(callback: SyncCallback) {
  syncTaskQueue.push(callback);
}

可以看到 scheduleSyncCallback本质就是把performWorkOnRoot 绑定root之后,推入一个数组等待运行

推入数组之后,调用flushSyncCallback启动微任务调度,其本质上就是把callback处理函数通过queueMicroTask排入微队列等待运行,只不过对不同环境进行了兼容,如下:

function _flushSyncCallbacks() {
  if (!isFlushingSyncQueue && syncTaskQueue.length > 0) {
    // 上锁
    isFlushingSyncQueue = true;

    try {
      // 执行任务
      syncTaskQueue.forEach((syncTask) => scheduleMicroTask(syncTask));
    } catch (e) {
      console.error("同步微任务队列执行错误,错误信息:", e);
    } finally {
      // 执行结束  释放锁 清空队列
      isFlushingSyncQueue = false;
      syncTaskQueue = [];
    }
  }
}

/** 兼容多种环境的microTask */
export const scheduleMicroTask = (
  typeof queueMicrotask === "function"
    ? queueMicrotask
    : typeof Promise === "function"
    ? (callback: SyncCallback) => Promise.resolve().then(callback)
    : setTimeout
) as (callback: SyncCallback) => void;

/** 微任务flush清空队列 */
export const flushSyncCallbacks = () => scheduleMicroTask(_flushSyncCallbacks);

_flushSyncCallback 函数会作为微任务执行,遍历数组内的performWorkOnRoot函数运行,最后置空数组。

如果优先级低于SyncLane

对于低于SyncLane的优先级lane,通过laneToSchedulerPriority转换成scheduler优先级并且交给scheduler调度运行。

 lane批处理

为什么lane可以实现优先级,同时保证在同步/异步代码中,都能保证异步执行的特性?

看一个例子

function Comp(){
    const [variableA,setVariableA] = useState<number>(0)
    return <Button onClick={e=>{
        setVariableA(100)
        console.log(variableA)
        setVariableA(200)
        console.log(variableA)
    }}></Button>
}

这个例子中,在点击按钮之后,react只会重新渲染一次,并且两次console输出,都是0

其原理是,在两次setVariableA时,setter函数会调用diapatchSetState函数,如下:

/** 派发修改state */
function dispatchSetState<State>(
  fiber: FiberNode,
  updateQueue: UpdateQueue<State>,
  action: Action<State>
) {
  // 获取一个优先级 根据 dispatchSetState 执行所在的上下文
  const lane = requestUpdateLane();
  // 创建一个update对象
  const update = new Update(action, lane);
  // 入队 并且加入到fiber上
  updateQueue.enqueue(update, fiber, lane);
  // 开启调度时,也需要传入当前优先级
  scheduleUpdateOnFiber(fiber, lane);
}

可以看到

1. 这个函数首先获取当前优先级,由于是在onClick上下文调用的,所以其优先级为SyncLane,因为onClick是个离散事件,优先级最高!

 2. 把action(第一次调用的值100)和lane传入Update构造函数创建update对象 

3. 把update对象推入当前hook的updateQueue (注意 hook对象也有updataQueue这个后面细说)

4. 开启调度

在ensureRootIsScheduled函数中,拿到最高优先级的lane,也就是SyncLane,并且将此次更新需要执行的performSyncWorkOnRoot函数推入微队列,等到同步任务运行结束后运行。

继续执行onClick函数,此时获取variableA,由于更新还没进行,此时variableA还是0

执行到第二个setter,同上面逻辑,再次获取variableA 依旧是0 因为更新还没进行

  • 此时的微任务队列包含两个更新任务,即两次performWorkOnRoot的执行
  • 此时的variableA对应的hooks的updateQueue包含两个update更新

当onClick函数结束,此时执行微任务。

performSyncWorkOnRoot逻辑如下:

/** 从root开始 处理同步任务 */
export function performSyncWorkOnRoot(root: FiberRootNode) {
  // 获取当前的优先级
  const lane = getNextLane(root);

  if (lane !== SyncLane) {
    /**
     * 这里 lane如果不是同步任务了,说明同步任务的lane已经被remove 应该执行低优先级的任务了
     *  此时应该停止执行当前任务 重新调度
     * 【实现同步任务的批处理,当第一次执行完之后 commit阶段remove SyncLane 这里就继续不下去了,
     * 后面微任务中的 performSyncWorkOnRoot都不执行了】
     */
    return ensureRootIsScheduled(root);
  }

  // 开始生成fiber 关闭并发模式
  const exitStatus = renderRoot(root, lane, false);
  switch (exitStatus) {
    // 注意 同步任务一次性执行完 不存在RootInComplete中断的情况
    case RootCompleted:
      // 执行成功 设置finishedWork 和 finishedLane 并且commit
      // 设置root.finishedWork
      root.finishedWork = root.current.alternate;
      root.finishedLane = lane;
      // 设置wipRootRenderLane = NoLane;
      wipRootRenderLane = NoLane;
      commitRoot(root);
    default:
    // TODO Suspense的情况
  }
}

第一个performSyncWorkOnRoot执行,运行到useState的时候,其内部逻辑会一次性把当前hook上updateQueue中的两个update都执行完,此时的variableA已经是200了

第一次update执行之后,commit阶段,会调用markRootFInished,把当前执行的Synclane,从root.pendingLanes中去除,此时pendingLanes上为空 即NoLanes

执行第二个performSynWorkOnRoot,此时getNextLane得到NoLane,已经没有任务需要运行了,此时lane !== SyncLane 函数结束运行,第二次更新终止。

这样就达到了批处理任务的效果,不论setVaribaleA多少次,react都会在第一次更新的时候处理完所有的update! 后面的更新都会忽略!

如果是异步任务,也都一样,可以对照代码走一遍异步任务的情况!

那么为什么settimeout中也是异步的,很简单,因为setVaribleA执行的时候 只是向hook.updateQueue中推入了一个更新对象,并没有真正的更新,所以不论在哪里运行setter 都是异步。(区分老版本根据过期时间判断)

总结一下,为什么lane能更好的实现批处理,你可以把lane想象成一个可以被消费的数据,在setState的时候,会在pendingLanes设置lane,但是相同优先级的多个setState只能设置一个lane,那么在更新(消费)的过程中,当第一个performWorkOnRoot消费完第一个lane之后,后面的更新获取不到lane就会结束运行,避免多次render。

同时 lane的每一项对独立,可以自由的决定先调度哪些批次的优先级更新,不用像expirationTime一样,必须按顺序调度!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值