点击进入React源码调试仓库。

在React的concurrent模式下,低优先级工作执行过程中,一旦有更高优先级的工作进来,那么这个低优先级的工作会被勾销,优先执行高优先级工作。等高优先级工作做完了,低优先级工作会被从新做一遍。

咱们用一个具体的例子来了解一下高优先级工作插队。

有这样一个组件,state为0,进入页面,会调用setState将state加1,这个作为低优先级工作。React开始进行更新,在这个低优先级工作尚未实现时,模仿按钮点击,state加2,这个作为高优先级工作。能够看到,页面上的数字变动为0 -> 2 -> 3,而不是0 -> 1 -> 3。这就阐明,当低优先级工作(加1)正在进行时,高优先级工作进来了,而它会把state设置为2。因为高优先级工作的插队,设置state为1的低优先级工作会被勾销,先做高优先级工作,所以数字从0变成了2。而高优先级工作实现之后,低优先级工作会被重做,所以state再从2加到了3。

景象如下:

利用chrome的性能剖析工具捕获更新过程,能够显著看到优先级插队的过程

残缺的profile文件我保留下来了,能够载入到chrome中具体查看:高优先级插队.json 。

能够再看一下这个过程中两个工作优先级在调度过程中的信息

点击查看 高优先级插队示例代码文件。

点击查看 低优先级工作饥饿问题示例代码文件。

接下来咱们就来从setState开始,探讨一下这种插队行为的实质,内容波及update对象的生成、发动调度、工作循环、高优工作插队、update对象的解决、低优先级工作重做等内容。

产生更新

当调用setState时,意味着组件对应的fiber节点产生了一个更新。setState实际上是生成一个update对象,调用enqueueSetState,将这个update对象连接到fiber节点的updateQueue链表中.

Component.prototype.setState = function(partialState, callback) {  this.updater.enqueueSetState(this, partialState, callback, 'setState');};

enqueueSetState的职责是创立update对象,将它入队fiber节点的update链表(updateQueue),而后发动调度。

  enqueueSetState(inst, payload, callback) {    // 获取以后触发更新的fiber节点。inst是组件实例    const fiber = getInstance(inst);    // eventTime是以后触发更新的工夫戳    const eventTime = requestEventTime();    const suspenseConfig = requestCurrentSuspenseConfig();    // 获取本次update的优先级    const lane = requestUpdateLane(fiber, suspenseConfig);    // 创立update对象    const update = createUpdate(eventTime, lane, suspenseConfig);    // payload就是setState的参数,回调函数或者是对象的模式。    // 解决更新时参加计算新状态的过程    update.payload = payload;    // 将update放入fiber的updateQueue    enqueueUpdate(fiber, update);    // 开始进行调度    scheduleUpdateOnFiber(fiber, lane, eventTime);  }

梳理一下enqueueSetState中具体做的事件:

找到fiber

首先获取产生更新的组件所对应的fiber节点,因为产生的update对象须要放到fiber节点的updateQueue上。而后获取以后这个update产生的工夫,这与更新的饥饿问题相干,咱们暂且不思考,而且下一步的suspenseConfig能够先疏忽。

计算优先级

之后比拟重要的是计算以后这个更新它的优先级lane:

const lane = requestUpdateLane(fiber, suspenseConfig);

计算这个优先级的时候,是如何决定依据什么货色去计算呢?这还得从React的合成事件说起。

事件触发时,合成事件机制调用scheduler中的runWithPriority函数,目标是以该交互事件对应的事件优先级去派发真正的事件流程。runWithPriority会将事件优先级转化为scheduler外部的优先级并记录下来。当调用requestUpdateLane计算lane的时候,会去获取scheduler中的优先级,以此作为lane计算的根据。

这部分的源码在这里

创立update对象, 入队updateQueue

依据lane和eventTime还有suspenseConfig,去创立一个update对象,构造如下:

const update: Update<*> = {  eventTime,  lane,  suspenseConfig,  tag: UpdateState,  payload: null,  callback: null,  next: null,};
  • eventTime:更新的产生工夫
  • lane:示意优先级
  • suspenseConfig:工作挂起相干
  • tag:示意更新是哪种类型(UpdateState,ReplaceState,ForceUpdate,CaptureUpdate)
  • payload:更新所携带的状态。

    • 在类组件中,有两种可能,对象({}),和函数((prevState, nextProps):newState => {})
    • 根组件中,为React.element,即ReactDOM.render的第一个参数
  • callback:可了解为setState的回调
  • next:指向下一个update的指针

再之后就是去调用React工作执行的入口函数:scheduleUpdateOnFiber去调度执行更新工作了。

当初咱们晓得了,产生更新的fiber节点上会有一个updateQueue,它蕴含了刚刚产生的update。上面该进入scheduleUpdateOnFiber了,开始进入真正的调度流程。通过调用scheduleUpdateOnFiber,render阶段的构建workInProgress树的工作会被调度执行,这个过程中,fiber上的updateQueue会被解决。

调度筹备

React的更新入口是scheduleUpdateOnFiber,它辨别update的lane,将同步更新和异步更新分流,让二者进入各自的流程。但在此之前,它会做几个比拟重要的工作:

  • 查看是否是有限更新,例如在render函数中调用了setState。
  • 从产生更新的节点开始,往上始终循环到root,目标是将fiber.lanes始终向上收集,收集到父级节点的childLanes中,childLanes是辨认这个fiber子树是否须要更新的要害。
  • 在root上标记更新,也就是将update的lane放到root.pendingLanes中,每次渲染的优先级基准:renderLanes就是取自root.pendingLanes中最紧急的那一部分lanes。

这三步能够视为更新执行前的筹备工作。

第1个能够避免死循环卡死的状况。

第2个,如果fiber.lanes不为空,则阐明该fiber节点有更新,而fiber.childLanes是判断以后子树是否有更新的重要依据,若有更新,则持续向下构建,否则间接复用已有的fiber树,就不往下循环了,能够屏蔽掉那些无需更新的fiber节点。

第3个是将以后update对象的lane退出到root.pendingLanes中,保障真正开始做更新工作的时候,获取到update的lane,从而作为本次更新的渲染优先级(renderLanes),去更新。

实际上在更新时候获取到的renderLanes,并不一定蕴含update对象的lane,因为有可能它只是一个较低优先级的更新,有可能在它后面有高优先级的更新

梳理完scheduleUpdateOnFiber的大抵逻辑之后,咱们来看一下它的源码:

export function scheduleUpdateOnFiber(  fiber: Fiber,  lane: Lane,  eventTime: number,) {  // 第一步,查看是否有有限更新  checkForNestedUpdates();  ...  // 第二步,向上收集fiber.childLanes  const root = markUpdateLaneFromFiberToRoot(fiber, lane);  ...  // 第三步,在root上标记更新,将update的lane放到root.pendingLanes  markRootUpdated(root, lane, eventTime);  ...  // 依据Scheduler的优先级获取到对应的React优先级  const priorityLevel = getCurrentPriorityLevel();  if (lane === SyncLane) {    // 本次更新是同步的,例如传统的同步渲染模式    if (      (executionContext & LegacyUnbatchedContext) !== NoContext &&      (executionContext & (RenderContext | CommitContext)) === NoContext    ) {      // 如果是本次更新是同步的,并且以后还未渲染,意味着主线程闲暇,并没有React的      // 更新工作在执行,那么调用performSyncWorkOnRoot开始执行同步工作      ...      performSyncWorkOnRoot(root);    } else {      // 如果是本次更新是同步的,不过以后有React更新工作正在进行,      // 而且因为无奈打断,所以调用ensureRootIsScheduled      // 目标是去复用曾经在更新的工作,让这个已有的工作      // 把这次更新顺便做了      ensureRootIsScheduled(root, eventTime);      ...    }  } else {    ...    // Schedule other updates after in case the callback is sync.    // 如果是更新是异步的,调用ensureRootIsScheduled去进入异步调度    ensureRootIsScheduled(root, eventTime);    schedulePendingInteractions(root, lane);  }  ...}
scheduleUpdateOnFiber 的残缺源码在这里,这里是第二步:markUpdateLaneFromFiberToRoot 和 第三步: markRootUpdated的残缺源码,我都做了正文。

通过了后面的筹备工作后,scheduleUpdateOnFiber最终会调用ensureRootIsScheduled,来让React工作被调度,这是一个十分重要的函数,它关乎等同或较低工作的收敛
高优先级工作插队工作饥饿问题,上面具体解说它。

开始调度

在开始解说ensureRootIsScheduled之前,咱们有必要弄清楚React的更新工作的实质。

React工作的实质

一个update的产生最终会使React在内存中依据现有的fiber树构建一棵新的fiber树,新的state的计算、diff操作、以及一些生命周期的调用,都会在这个构建过程中进行。这个整体的构建工作被称为render阶段,这个render阶段整体就是一个残缺的React更新工作,更新工作能够看作执行一个函数,这个函数在concurrent模式下就是performConcurrentWorkOnRoot,更新工作的调度能够看成是这个函数被scheduler依照工作优先级安顿它何时执行。

Scheduler的调度和React的调度是两个齐全不同的概念,React的调度是协调工作进入哪种Scheduler的调度模式,它的调度并不波及工作的执行,而Scheduler是调度机制的真正外围,它是实打实地去执行工作,没有它,React的工作再重要也无奈执行,心愿读者加以辨别这两种概念。

当一个工作被调度之后,scheduler就会生成一个工作对象(task),它的构造如下所示,除了callback之外临时不须要关怀其余字段的含意。

  var newTask = {    id: taskIdCounter++,    // 工作函数,也就是 performConcurrentWorkOnRoot    callback,    // 任务调度优先级,由行将讲到的工作优先级转化而来    priorityLevel,    // 工作开始执行的工夫点    startTime,    // 工作的过期工夫    expirationTime,    // 在小顶堆工作队列中排序的根据    sortIndex: -1,  };

每当生成了一个这样的工作,它就会被挂载到root节点的callbackNode属性上,以示意以后曾经有工作被调度了,同时会将工作优先级存储到root的callbackPriority上,
示意如果有新的工作进来,必须用它的工作优先级和已有工作的优先级(root.callbackPriority)比拟,来决定是否有必要勾销曾经有的工作。

所以在调度工作的时候,工作优先级是不可或缺的一个重要角色。

工作优先级

工作自身是由更新产生的,因而工作优先级实质上是和update的优先级,即update.lane无关(只是无关,不肯定是由它而来)。得出的工作优先级属于lanePriority,它不是update的lane,而且与scheduler外部的调度优先级是两个概念,React中的优先级转化关系能够看我总结过的一篇文章:React中的优先级,咱们这里只探讨工作优先级的生成过程。

调度筹备 的最初提到过,update.lane会被放入root.pendingLanes,随后会获取root.pendingLanes中最优先级的那些lanes作为renderLanes。工作优先级的生成就产生在计算renderLanes的阶段,工作优先级其实就是renderLanes对应的lanePriority。因为renderLanes是本次更新的优先级基准,所以它对应的lanePriority被作为工作优先级来掂量本次更新工作的优先级权重理所应当。

root.pendingLanes,蕴含了以后fiber树中所有待处理的update的lane。

工作优先级有三类:

  • 同步优先级:React传统的同步渲染模式产生的更新工作所持有的优先级
  • 同步批量优先级:同步模式到concurrent模式过渡模式:blocking模式(介绍)产生的更新工作所持有的优先级
  • concurrent模式下的优先级:concurrent模式产生的更新持有的优先级

最右面的两个lane别离为同步优先级和同步批量优先级,剩下右边的lane简直所有都和concurrent模式无关。

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;concurrent模式下的lanes:/*                               */ 0b1111111111111111111111111111100;

计算renderLanes的函数是getNextLanes,生成工作优先级的函数是getHighestPriorityLanes

工作优先级决定着工作在React中被如何调度,而由工作优先级转化成的任务调度优先级(下面给出的scheduler的task构造中的priorityLevel),
决定着Scheduler何时去解决这个工作。

任务调度协调 - ensureRootIsScheduled

目前为止咱们理解了工作和工作优先级的实质,上面正式进入工作的调度过程。React这边对工作的调度实质上其实是以工作优先级为基准,去操作多个或单个工作。

多个工作的状况,绝对于新工作,会对现有工作进行或复用,或勾销的操作,单个工作的状况,对工作进行或同步,或异步,或批量同步(临时不须要关注) 的调度决策,
这种行为能够看成是一种任务调度协调机制,这种协调通过ensureRootIsScheduled去实现。

让咱们看一看ensureRootIsScheduled函数做的事件,先是筹备本次任务调度协调所须要的lanes和工作优先级,而后判断是否真的须要调度

  • 获取root.callbackNode,即旧工作
  • 查看工作是否过期,将过期工作放入root.expiredLanes,目标是让过期工作可能以同步优先级去进入调度(立刻执行)
  • 获取renderLanes(优先从root.expiredLanes获取),如果renderLanes是空的,阐明不须要调度,间接return掉
  • 获取本次工作,即新工作的优先级:newCallbackPriority

接下来是协调任务调度的过程:

  • 首先判断是否有必要发动一次新调度,办法是通过比拟新工作的优先级和旧工作的优先级是否相等:

    • 相等,则阐明无需再次发动一次调度,间接复用旧工作即可,让旧工作在解决更新的时候顺便把新工作给做了。
    • 不相等,则阐明新工作的优先级肯定高于旧工作,这种状况就是高优先级工作插队,须要把旧工作勾销掉。
  • 真正发动调度,看新工作的工作优先级:

    • 同步优先级:调用scheduleSyncCallback去同步执行工作。
    • 同步批量执行:调用scheduleCallback将工作以立刻执行的优先级去退出调度。
    • 属于concurrent模式的优先级:调用scheduleCallback将工作以下面获取到的新工作优先级去退出调度。

这里有两点须要阐明:

  1. 为什么新旧工作的优先级如果不相等,那么新工作的优先级肯定高于旧工作?

这是因为每次调度去获取工作优先级的时候,都只获取root.pendingLanes中最紧急的那局部lanes对应的优先级,低优先级的update持有的lane对应的优先级是无奈被获取到的。通过这种方法,能够将来自同一事件中的多个更新收敛到一个工作中去执行,话中有话就是同一个事件触发的屡次更新的优先级是一样的,没必要发动屡次任务调度。例如在一个事件中屡次调用setState:

class Demo extends React.Component {  state = {    count: 0  }  onClick = () => {    this.setState({ count: 1 })    this.setState({ count: 2 })  }  render() {    return <button onClick={onClick}>{this.state.count}</button>  }}

页面上会间接显示出2,尽管onClick事件调用了两次setState,但只会引起一次调度,设置count为2的那次调度被因为优先级与设置count为1的那次工作的优先级雷同,
所以没有去再次发动调度,而是复用了已有工作。这是React17对于屡次setState优化实现的扭转,之前是通过batchingUpdate这种机制实现的。

  1. 三种工作优先级的调度模式有何区别,行为表现上如何?
  • 同步优先级:传统的React同步渲染模式和过期工作的调度。通过React提供的scheduleSyncCallback函数将工作函数performSyncWorkOnRoot退出到React本人的同步队列(syncQueue)中,之后以ImmediateSchedulerPriority的优先级将循环执行syncQueue的函数退出到scheduler中,目标是让工作在下一次事件循环中被执行掉。然而因为React的管制,这种模式下的工夫片会在工作都执行完之后再去查看,体现为没有工夫片。
  • 同步批量执行:同步渲染模式到concurrent渲染模式的过渡模式blocking模式,会将工作函数performSyncWorkOnRoot以ImmediateSchedulerPriority的优先级退出到scheduler中,也是让工作在下一次事件循环中被执行掉,也不会有工夫片的体现。
  • 属于concurrent模式的优先级:将工作函数performConcurrentWorkOnRoot以工作本人的优先级退出到scheduler中,scheduler外部的会通过这个优先级管制该工作在scheduler外部工作队列中的排序,从而决定工作适合被执行,而且工作真正执行时会有工夫片的体现,能够施展出scheduler异步可中断调度的真正威力。
要留神一点,用来做新旧工作比拟的优先级与这里将工作退出到scheduler中传入的优先级不是一个,后者可由前者通过lanePriorityToSchedulerPriority转化而来。

通过以上的剖析,置信大家曾经对ensureRootIsScheduled的运行机制比拟清晰了,当初让咱们看一下它的实现:

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {  // 获取旧工作  const existingCallbackNode = root.callbackNode;  // 记录工作的过期工夫,查看是否有过期工作,有则立刻将它放到root.expiredLanes,  // 便于接下来将这个工作以同步模式立刻调度  markStarvedLanesAsExpired(root, currentTime);  // 获取renderLanes  const nextLanes = getNextLanes(    root,    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,  );  // 获取renderLanes对应的工作优先级  const newCallbackPriority = returnNextLanesPriority();  if (nextLanes === NoLanes) {    // 如果渲染优先级为空,则不须要调度    if (existingCallbackNode !== null) {      cancelCallback(existingCallbackNode);      root.callbackNode = null;      root.callbackPriority = NoLanePriority;    }    return;  }  // 如果存在旧工作,那么看一下是否复用  if (existingCallbackNode !== null) {    // 获取旧工作的优先级    const existingCallbackPriority = root.callbackPriority;    // 如果新旧工作的优先级雷同,则无需调度    if (existingCallbackPriority === newCallbackPriority) {      return;    }    // 代码执行到这里阐明新工作的优先级高于旧工作的优先级    // 勾销掉旧工作,实现高优先级工作插队    cancelCallback(existingCallbackNode);  }  // 调度一个新工作  let newCallbackNode;  if (newCallbackPriority === SyncLanePriority) {    // 若新工作的优先级为同步优先级,则同步调度,传统的同步渲染和过期工作会走这里    newCallbackNode = scheduleSyncCallback(      performSyncWorkOnRoot.bind(null, root),    );  } else if (newCallbackPriority === SyncBatchedLanePriority) {    // 同步模式到concurrent模式的过渡模式:blocking模式会走这里    newCallbackNode = scheduleCallback(      ImmediateSchedulerPriority,      performSyncWorkOnRoot.bind(null, root),    );  } else {    // concurrent模式的渲染会走这里    // 依据工作优先级获取Scheduler的调度优先级    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(      newCallbackPriority,    );    // 计算出调度优先级之后,开始让Scheduler调度React的更新工作    newCallbackNode = scheduleCallback(      schedulerPriorityLevel,      performConcurrentWorkOnRoot.bind(null, root),    );  }  // 更新root上的工作优先级和工作,以便下次发动调度时候能够获取到  root.callbackPriority = newCallbackPriority;  root.callbackNode = newCallbackNode;}

ensureRootIsScheduled实际上是在任务调度层面整合了高优先级工作的插队和工作饥饿问题的要害逻辑,这只是宏观层面的决策,决策背地的起因是React解决更新时
对于不同优先级的update的取舍以及对root.pendingLanes的标记操作,这须要咱们下沉到执行更新工作的过程中。

解决更新

一旦有更新产生,update对象就会被放入updateQueue并挂载到fiber节点上。构建fiber树时,会带着renderLanes去解决updateQueue,在beginWork阶段,对于类组件
会调用processUpdateQueue函数,一一解决这个链表上的每个update对象,计算新的状态,一旦update持有的优先级不够,那么就会跳过这个update的解决,并把这个被跳过的update的lane放到fiber.lanes中,好在completeWork阶段收集起来。

循环updateQueue去计算状态的过程实际上较为简单,因为低优先级update会被跳过并且会重做,所以这波及到最终状态对立的问题,对于这一过程的原理解读在我的这篇文章里:扒一扒React计算状态的原理,在本篇文章中只关注优先级相干的局部。

对于优先级的局部比拟好了解,就是只解决优先级足够的update,跳过那些优先级有余的update,并且将这些update的lane放到fiber.lanes中。咱们间接来看一下实现:

function processUpdateQueue<State>(  workInProgress: Fiber,  props: any,  instance: any,  renderLanes: Lanes,): void {  ...  if (firstBaseUpdate !== null) {    let update = firstBaseUpdate;    do {      const updateLane = update.lane;      // isSubsetOfLanes函数的意义是,判断以后更新的优先级(updateLane)      // 是否在渲染优先级(renderLanes)中如果不在,那么就阐明优先级有余      if (!isSubsetOfLanes(renderLanes, updateLane)) {        ...        /*        *        * newLanes会在最初被赋值到workInProgress.lanes上,而它又最终        * 会被收集到root.pendingLanes。        *        * 再次更新时会从root上的pendingLanes中找出应该在本次中更新的优先        * 级(renderLanes),renderLanes含有本次跳过的优先级,再次进入,        * processUpdateQueue wip的优先级符合要求,被更新掉,低优先级工作        * 因而被重做        * */        newLanes = mergeLanes(newLanes, updateLane);      } else {        // 优先级足够,去计算state        ...      }    } while (true);    // 将newLanes赋值给workInProgress.lanes,    // 就是将被跳过的update的lane放到fiber.lanes    workInProgress.lanes = newLanes;  }}

只解决优先级足够的update是让高优先级工作被执行掉的最实质起因,在循环了一次updateQueue之后,那些被跳过的update的lane又被放入了fiber.lanes,当初,只须要将它放到root.pendingLanes中,就能示意在本轮更新后,依然有工作未被解决,从而实现低优先级工作被从新调度。所以接下来的过程就是fiber节点的实现阶段:completeWork阶段去收集这些lanes。

收集未被解决的lane

在completeUnitOfWork的时候,fiber.lanes 和 childLanes被一层一层收集到父级fiber的childLanes中,该过程产生在completeUnitOfWork函数中调用的resetChildLanes,它循环fiber节点的子树,将子节点及其兄弟节点中的lanes和childLanes收集到以后正在complete阶段的fiber节点上的childLanes。

假如第3层中的<List/><Table/>组件都别离有update因为优先级不够而被跳过,那么在它们父级的div fiber节点completeUnitOfWork的时候,会调用resetChildLanes
把它俩的lanes收集到div fiber.childLanes中,最终把所有的lanes收集到root.pendingLanes.

                                    root(pendingLanes: 0b01110)                                     |  1                                  App                                     |                                     |  2 compeleteUnitOfWork-----------> div (childLanes: 0b01110)                                     /                                    /  3                              <List/> ---------> <Table/> --------> p                            (lanes: 0b00010)   (lanes: 0b00100)                         (childLanes: 0b01000)       /                                 /                   /                                /                   /  4                            p                   ul                                                  /                                                 /                                                li ------> li

在每一次往上循环的时候,都会调用resetChildLanes,目标是将fiber.childLanes层层收集。

function completeUnitOfWork(unitOfWork: Fiber): void {  // 曾经完结beginWork阶段的fiber节点被称为completedWork  let completedWork = unitOfWork;  do {    // 向上始终循环到root的过程    ...    // fiber节点的.flags上没有Incomplete,阐明是失常实现了工作    if ((completedWork.flags & Incomplete) === NoFlags) {      ...      // 调用resetChildLanes去收集lanes      resetChildLanes(completedWork);      ...    } else {/*...*/}    ...  } while (completedWork !== null);  ...}

resetChildLanes中只收集以后正在complete的fiber节点的子节点和兄弟节点的lanes以及childLanes:

function resetChildLanes(completedWork: Fiber) {  ...  let newChildLanes = NoLanes;  if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {    // profile相干,无需关注  } else {    // 循环子节点和兄弟节点,收集lanes    let child = completedWork.child;    while (child !== null) {      // 收集过程      newChildLanes = mergeLanes(        newChildLanes,        mergeLanes(child.lanes, child.childLanes),      );      child = child.sibling;    }  }  // 将收集到的lanes放到该fiber节点的childLanes中  completedWork.childLanes = newChildLanes;}

最初将这些收集到的childLanes放到root.pendingLanes的过程,是产生在本次更新的commit阶段中,因为render阶段的渲染优先级来自root.pendingLanes,不能随便地批改它。所以要在render阶段之后的commit阶段去批改。咱们看一下commitRootImpl中这个过程的实现:

function commitRootImpl(root, renderPriorityLevel) {  // 将收集到的childLanes,连同root本人的lanes,一并赋值给remainingLanes  let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);  // markRootFinished中会将remainingLanes赋值给remainingLanes  markRootFinished(root, remainingLanes);  ...}

从新发动调度

至此,咱们将低优先级工作的lane从新收集到了root.pendingLanes中,这时只须要再发动一次调度就能够了,通过在commit阶段再次调用ensureRootIsScheduled去实现,这样就又会走一遍调度的流程,低优先级工作被执行。

function commitRootImpl(root, renderPriorityLevel) {  // 将收集到的childLanes,连同root本人的lanes,一并赋值给remainingLanes  let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);  // markRootFinished中会将remainingLanes赋值给remainingLanes  markRootFinished(root, remainingLanes);  ...  // 在每次所有更新实现的时候都会调用这个ensureRootIsScheduled  // 以保障root上任何的pendingLanes都能被解决  ensureRootIsScheduled(root, now());}

总结

高优先级工作插队,低优先级工作重做的整个过程共有四个关键点:

  • ensureRootIsScheduled勾销已有的低优先级更新工作,从新调度一个工作去做高优先级更新,并以root.pendingLanes中最重要的那局部lanes作为渲染优先级
  • 执行更新工作时跳过updateQueue中的低优先级update,并将它的lane标记到fiber.lanes中。
  • fiber节点的complete阶段收集fiber.lanes到父级fiber的childLanes,始终到root。
  • commit阶段将所有root.childLanes连同root.lanes一并赋值给root.pendingLanes。
  • commit阶段的最初从新发动调度。

整个流程始终以高优先级工作为重,顾全大局,最可能体现React晋升用户体验的信心。

欢送扫码关注公众号,发现更多技术文章