React源码解析之scheduleWork(下)

  • 2019 年 10 月 6 日
  • 笔记

上篇回顾: React源码解析之scheduleWork(上)

八、scheduleCallbackForRoot() 作用:render()之后,立即执行调度任务

源码:

// Use this function, along with runRootCallback, to ensure that only a single  // callback per root is scheduled. It's still possible to call renderRoot  // directly, but scheduling via this function helps avoid excessive callbacks.  // It works by storing the callback node and expiration time on the root. When a  // new callback comes in, it compares the expiration time to determine if it  // should cancel the previous one. It also relies on commitRoot scheduling a  // callback to render the next level, because that means we don't need a  // separate callback per expiration time.  //同步调用callback  //流程是在root上存取callback和expirationTime,  // 当新的callback调用时,比较更新expirationTime  function scheduleCallbackForRoot(    root: FiberRoot,    priorityLevel: ReactPriorityLevel,    expirationTime: ExpirationTime,  ) {    //获取root的回调过期时间    const existingCallbackExpirationTime = root.callbackExpirationTime;    //更新root的回调过期时间    if (existingCallbackExpirationTime < expirationTime) {      // New callback has higher priority than the existing one.      //当新的expirationTime比已存在的callback的expirationTime优先级更高的时候      const existingCallbackNode = root.callbackNode;      if (existingCallbackNode !== null) {        //取消已存在的callback(打断)        //将已存在的callback节点从链表中移除        cancelCallback(existingCallbackNode);      }      //更新callbackExpirationTime      root.callbackExpirationTime = expirationTime;      //如果是同步任务      if (expirationTime === Sync) {        // Sync React callbacks are scheduled on a special internal queue        //在临时队列中同步被调度的callback        root.callbackNode = scheduleSyncCallback(          runRootCallback.bind(            null,            root,            renderRoot.bind(null, root, expirationTime),          ),        );      } else {        let options = null;        if (expirationTime !== Never) {          //(Sync-2 - expirationTime) * 10-now()          let timeout = expirationTimeToMs(expirationTime) - now();          options = {timeout};        }        //callbackNode即经过处理包装的新task        root.callbackNode = scheduleCallback(          priorityLevel,          //bind()的意思是绑定this,xx.bind(y)()这样才算执行          runRootCallback.bind(            null,            root,            renderRoot.bind(null, root, expirationTime),          ),          options,        );        if (          enableUserTimingAPI &&          expirationTime !== Sync &&          (executionContext & (RenderContext | CommitContext)) === NoContext        ) {          // Scheduled an async callback, and we're not already working. Add an          // entry to the flamegraph that shows we're waiting for a callback          // to fire.          //开始调度callback的标志          startRequestCallbackTimer();        }      }    }      // Associate the current interactions with this new root+priority.    //跟踪这些update,并计数、检测它们是否会报错    schedulePendingInteractions(root, expirationTime);  }  

解析: Fiber机制可以为每一个update任务进行优先级排序,并且可以记录调度到了哪里(schedulePendingInteractions())

同时,还可以中断正在执行的任务,优先执行优先级比当前高的任务(scheduleCallbackForRoot()),之后,还可以继续之前中断的任务,而React16 之前调用setState(),必须等待setStateupdate队列全部调度完,才能进行之后的操作。

一起看下scheduleCallbackForRoot()做了什么: (1)当新的scheduleCallback的优先级更高时,中断当前任务cancelCallback(existingCallbackNode) (2)如果是同步任务,则在临时队列中进行调度 (3)如果是异步任务,则更新调度队列的状态 (4)设置开始调度的时间节点 (5)跟踪调度的任务

具体讲解,请耐心往下看

九、cancelCallback() 作用: 中断正在执行的调度任务

源码:

const {    unstable_cancelCallback: Scheduler_cancelCallback,  } = Scheduler;    //从链表中移除task节点  function unstable_cancelCallback(task) {    //获取callbackNode的next节点    var next = task.next;    //由于链表是双向循环链表,一旦next是null则证明该节点已不存在于链表中    if (next === null) {      // Already cancelled.      return;    }    //自己等于自己,说明链表中就这一个callback节点    //firstTask/firstDelayedTask应该是类似游标的概念,即正要执行的节点    if (task === next) {      //置为null,即删除callback节点      //重置firstTask/firstDelayedTask      if (task === firstTask) {        firstTask = null;      } else if (task === firstDelayedTask) {        firstDelayedTask = null;      }    } else {      //将firstTask/firstDelayedTask指向下一节点      if (task === firstTask) {        firstTask = next;      } else if (task === firstDelayedTask) {        firstDelayedTask = next;      }      var previous = task.previous;      //熟悉的链表操作,删除已存在的callbackNode      previous.next = next;      next.previous = previous;    }      task.next = task.previous = null;  }  

解析: 操作schedule链表,将正要执行的callback“移除”,将游标指向下一个调度任务

十、scheduleSyncCallback() 作用: 如果是同步任务的话,则执行scheduleSyncCallback(),将调度任务入队,并返回入队后的临时队列

源码:

//入队callback,并返回临时的队列  export function scheduleSyncCallback(callback: SchedulerCallback) {    // Push this callback into an internal queue. We'll flush these either in    // the next tick, or earlier if something calls `flushSyncCallbackQueue`.    //在下次调度或调用 刷新同步回调队列 的时候刷新callback队列      //如果同步队列为空的话,则初始化同步队列,    //并在下次调度的一开始就刷新队列    if (syncQueue === null) {      syncQueue = [callback];      // Flush the queue in the next tick, at the earliest.      immediateQueueCallbackNode = Scheduler_scheduleCallback(        //赋予调度立即执行的高权限        Scheduler_ImmediatePriority,        flushSyncCallbackQueueImpl,      );    }    //如果同步队列不为空的话,则将callback入队    else {      // Push onto existing queue. Don't need to schedule a callback because      // we already scheduled one when we created the queue.      //在入队的时候,不必去调度callback,因为在创建队列的时候就已经调度了      syncQueue.push(callback);    }    //fake我认为是临时队列的意思    return fakeCallbackNode;  }  

解析: (1)当同步队列为空 调用Scheduler_scheduleCallback(),将该callback任务入队,并把该callback包装成newTask,赋给root.callbackNode

Scheduler_scheduleCallback():

const {    unstable_scheduleCallback: Scheduler_scheduleCallback,  } = Scheduler;    //返回经过包装处理的task  function unstable_scheduleCallback(priorityLevel, callback, options) {    var currentTime = getCurrentTime();      var startTime;    var timeout;      //更新startTime(默认是现在)和timeout(默认5s)    if (typeof options === 'object' && options !== null) {      var delay = options.delay;      if (typeof delay === 'number' && delay > 0) {        startTime = currentTime + delay;      } else {        startTime = currentTime;      }      timeout =        typeof options.timeout === 'number'          ? options.timeout          : timeoutForPriorityLevel(priorityLevel);    } else {      // Times out immediately      // var IMMEDIATE_PRIORITY_TIMEOUT = -1;      // Eventually times out      // var USER_BLOCKING_PRIORITY = 250;      //普通优先级的过期时间是5s      // var NORMAL_PRIORITY_TIMEOUT = 5000;      //低优先级的过期时间是10s      // var LOW_PRIORITY_TIMEOUT = 10000;        timeout = timeoutForPriorityLevel(priorityLevel);      startTime = currentTime;    }    //过期时间是当前时间+5s,也就是默认是5s后,react进行更新    var expirationTime = startTime + timeout;    //封装成新的任务    var newTask = {      callback,      priorityLevel,      startTime,      expirationTime,      next: null,      previous: null,    };    //如果开始调度的时间已经错过了    if (startTime > currentTime) {      // This is a delayed task.      //将延期的callback插入到延期队列中      insertDelayedTask(newTask, startTime);      //如果调度队列的头任务没有,并且延迟调度队列的头任务正好是新任务,      //说明所有任务均延期,并且此时的任务是第一个延期任务      if (firstTask === null && firstDelayedTask === newTask) {        // All tasks are delayed, and this is the task with the earliest delay.        //如果延迟调度开始的flag为true,则取消定时的时间        if (isHostTimeoutScheduled) {          // Cancel an existing timeout.          cancelHostTimeout();        }        //否则设为true        else {          isHostTimeoutScheduled = true;        }        // Schedule a timeout.        requestHostTimeout(handleTimeout, startTime - currentTime);      }    }    //没有延期的话,则按计划插入task    else {      insertScheduledTask(newTask, expirationTime);      // Schedule a host callback, if needed. If we're already performing work,      // wait until the next time we yield.      //更新调度执行的标志      if (!isHostCallbackScheduled && !isPerformingWork) {        isHostCallbackScheduled = true;        requestHostCallback(flushWork);      }    }    //返回经过包装处理的task    return newTask;  }  

当有新的update时,React 默认是 5s 后进行更新(直观地说,就是你更新了开发代码,过 5s,也可以说是最迟过了 5s,网页更新)

Scheduler_scheduleCallback()的作用是: ① 确定当前时间startTime延迟更新时间timeout ② 新建newTask对象(包含callbackexpirationTime) ③ 如果是延迟调度的话,将newTask放入【延迟调度队列】 ④ 如果是正常调度的话,将newTask放入【正常调度队列】 ⑤ 返回包装的newTask

(2)当同步队列不为空 将该callback入队

scheduleSyncCallback()最终返回临时回调节点。


十一、scheduleCallback() 作用: 如果是异步任务的话,则执行scheduleCallback(),对callback进行包装处理,并更新调度队列的状态

源码:

//对callback进行包装处理,并更新调度队列的状态  export function scheduleCallback(    reactPriorityLevel: ReactPriorityLevel,    callback: SchedulerCallback,    options: SchedulerCallbackOptions | void | null,  ) {    //获取调度的优先级    const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);    return Scheduler_scheduleCallback(priorityLevel, callback, options);  }  

解析:一样,调用Scheduler_scheduleCallback(),将该callback任务入队,并把该callback包装成newTask,赋给root.callbackNode

tips: func.bind(xx)的意思是func里的this绑定的是xx, 也就是说 是xx调用func方法

注意!func.bind(xx)这仅仅是绑定,而不是调用!

func.bind(xx)()这样才算是xx调用func方法!

至此,scheduleCallbackForRoot()已分析完毕(八到十一)

我们讲到这里了:

export function scheduleUpdateOnFiber(){    xxx    xxx    xxx    if (expirationTime === Sync) {      if( 第一次render ){        xxx      }else{        /*八到十一讲解的内容*/        scheduleCallbackForRoot(root, ImmediatePriority, Sync);        //当前没有update时        if (executionContext === NoContext) {          //刷新同步任务队列          flushSyncCallbackQueue();        }      }    }  }  

十二、flushSyncCallbackQueue() 作用: 更新同步任务队列的状态

源码:

//刷新同步任务队列  export function flushSyncCallbackQueue() {    //如果即时节点存在则中断当前节点任务,从链表中移除task节点    if (immediateQueueCallbackNode !== null) {      Scheduler_cancelCallback(immediateQueueCallbackNode);    }    //更新同步队列    flushSyncCallbackQueueImpl();  }  

flushSyncCallbackQueueImpl():

//更新同步队列  function flushSyncCallbackQueueImpl() {    //如果同步队列未更新过并且同步队列不为空    if (!isFlushingSyncQueue && syncQueue !== null) {      // Prevent re-entrancy.      //防止重复执行,相当于一把锁      isFlushingSyncQueue = true;      let i = 0;      try {        const isSync = true;        const queue = syncQueue;        //遍历同步队列,并更新刷新的状态isSync=true        runWithPriority(ImmediatePriority, () => {          for (; i < queue.length; i++) {            let callback = queue[i];            do {              callback = callback(isSync);            } while (callback !== null);          }        });        //遍历结束后置为null        syncQueue = null;      } catch (error) {        // If something throws, leave the remaining callbacks on the queue.        if (syncQueue !== null) {          syncQueue = syncQueue.slice(i + 1);        }        // Resume flushing in the next tick        Scheduler_scheduleCallback(          Scheduler_ImmediatePriority,          flushSyncCallbackQueue,        );        throw error;      } finally {        isFlushingSyncQueue = false;      }    }  }  

解析: 当前调度的任务被中断时,先从链表中“移除”当前节点,并调用flushSyncCallbackQueueImpl ()任务更新同步队列

循环遍历syncQueue,并更新节点的isSync状态(isSync=true)


然后到这里:

export function scheduleUpdateOnFiber(){    xxx    xxx    xxx    if (expirationTime === Sync) {      if( 第一次render ){        xxx      }else{        /*八到十一讲解的内容*/        scheduleCallbackForRoot(root, ImmediatePriority, Sync);        if (executionContext === NoContext) {          //十二讲的内容          flushSyncCallbackQueue();        }      }    }    //如果是异步任务的话,则立即执行调度任务    //对应if (expirationTime === Sync)    else {      scheduleCallbackForRoot(root, priorityLevel, expirationTime);    }      if (      (executionContext & DiscreteEventContext) !== NoContext &&      // Only updates at user-blocking priority or greater are considered      // discrete, even inside a discrete event.      // 只有在用户阻止优先级或更高优先级的更新才被视为离散,即使在离散事件中也是如此      (priorityLevel === UserBlockingPriority ||        priorityLevel === ImmediatePriority)    ) {      // This is the result of a discrete event. Track the lowest priority      // discrete update per root so we can flush them early, if needed.      //这是离散事件的结果。 跟踪每个根的最低优先级离散更新,以便我们可以在需要时尽早清除它们。      //如果rootsWithPendingDiscreteUpdates为null,则初始化它      if (rootsWithPendingDiscreteUpdates === null) {        //key是root,value是expirationTime        rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);      } else {        //获取最新的DiscreteTime        const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);        //更新DiscreteTime        if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {          rootsWithPendingDiscreteUpdates.set(root, expirationTime);        }      }    }    }  

scheduleUpdateOnFiber()的最后这一段没看懂是什么意思,猜测是调度结束之前,更新离散时间。


十三、scheduleWork流程图

GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js


觉得不错的话,在下方留言都是一种支持 (●'◡'●)ノ

(完)