美文网首页
React源码解析之flushWork

React源码解析之flushWork

作者: 小进进不将就 | 来源:发表于2019-10-27 13:51 被阅读0次

    前言:
    先看一下flushWorkReact源码解析之requestHostCallback 中哪里用到了:

    xxx
    requestHostCallback(flushWork)
    xxx
    xxx
    requestHostCallback = function(callback) {
      scheduledHostCallback = callback;
      
    }
    ...
    ...
    channel.port1.onmessage = function(event) {
      xxx
      xxx
      const hasMoreWork = scheduledHostCallback(
              hasTimeRemaining,
              currentTime,
            );
      xxx
      xxx
    }
    

    flushWork=>callback=>scheduledHostCallback=>hasMoreWork
    对,就是这个hasMoreWork调用了flushWork,本文就讲解下flushWork()方法。

    一、flushWork
    作用:
    刷新调度队列,执行调度任务

    源码:

    //const hasTimeRemaining = frameDeadline - currentTime > 0
    //hasTimeRemaining 是每一帧内留给 react 的时间
    //initialTime 即 currentTime
    function flushWork(hasTimeRemaining, initialTime) {
      // Exit right away if we're currently paused
      //如果 React 没有掌握浏览器的控制权,则不执行调度任务
      if (enableSchedulerDebugging && isSchedulerPaused) {
        return;
      }
    
      // We'll need a host callback the next time work is scheduled.
      //调度任务执行的标识
      //调度任务是否执行
      isHostCallbackScheduled = false;
      //调度任务是否超时
      //一旦超时,
      if (isHostTimeoutScheduled) {
        // We scheduled a timeout but it's no longer needed. Cancel it.
        isHostTimeoutScheduled = false;
        /*cancelHostCallback*/
        cancelHostTimeout();
      }
    
      let currentTime = initialTime;
      // 检查是否有不过期的任务,并把它们加入到新的调度队列中
      advanceTimers(currentTime);
      /*isExecutingCallback 是否正在调用callback*/
      isPerformingWork = true;
      try {
        // 如果在一帧内执行时间超时,没有时间让 React 执行调度任务的话
        if (!hasTimeRemaining) {
          // Flush all the expired callbacks without yielding.
          // TODO: Split flushWork into two separate functions instead of using
          // a boolean argument?
          //一直执行过期的任务,直到到达一个不过期的任务为止
          while (
            /*firstTask即firstCallbackNode*/
            firstTask !== null &&
            //如果firstTask.expirationTime一直小于等于currentTime的话,则一直执行flushTask方法
            firstTask.expirationTime <= currentTime &&
            !(enableSchedulerDebugging && isSchedulerPaused)
          ) {
            /*flushTask即flushFirstCallback*/
            flushTask(firstTask, currentTime);
            currentTime = getCurrentTime();
            //检查是否有不过期的任务,并把它们加入到新的调度队列中
            advanceTimers(currentTime);
          }
        } else {
          // Keep flushing callbacks until we run out of time in the frame.
          //除非在一帧内执行时间超时,否则一直刷新 callback 队列
          //仍有时间剩余并且旧调度队列不为空时,将不过期的任务加入到新的调度队列中
          if (firstTask !== null) {
            do {
              flushTask(firstTask, currentTime);
              currentTime = getCurrentTime();
              advanceTimers(currentTime);
            } while (
              firstTask !== null &&
              !shouldYieldToHost() &&
              !(enableSchedulerDebugging && isSchedulerPaused)
            );
          }
        }
        // Return whether there's additional work
        if (firstTask !== null) {
          return true;
        } else {
          if (firstDelayedTask !== null) {
            // 执行延期的任务
            requestHostTimeout(
              handleTimeout,
              firstDelayedTask.startTime - currentTime,
            );
          }
          return false;
        }
      } finally {
        isPerformingWork = false;
      }
    }
    

    解析:
    (1) 判断 React 是否掌握浏览器的控制权,如果没有,则不执行调度任务

    (2) 如果调度任务超时,则调用cancelHostTimeout(),取消执行调度任务

    (3) 调用advanceTimers(),检查是否有不过期的任务,并把它们加入到新的调度队列中

    (4) 如果能执行到此步,意味着可以执行调度任务,设isPerformingWorktrue

    (5) 如果 React 的执行时间没有剩余,但是调度队列存在,且调度任务过期时
    ① 调用flushTask(),将调度任务从调度队列中拿出并执行,之后将调度任务生出的子调度任务插入到其后
    ② 调用getCurrentTime(),刷新当前时间
    ③ 调用advanceTimers(),检查是否有不过期的任务,并把它们加入到新的调度队列中

    (6) 如果 React 的执行时间有剩余,但是调度队列存在,且调度任务未被中断时
    ① 调用flushTask(),将调度任务从调度队列中拿出并执行,执行调度任务生出的子调度任务
    ② 调用getCurrentTime(),刷新当前时间
    ③ 调用advanceTimers(),检查是否有不过期的任务,并把它们加入到新的调度队列中

    (7) 如果调度任务都执行完毕,则返回 true,否则返回 false,执行延期的任务

    二、cancelHostTimeout
    作用:
    取消执行调度任务

    源码:

      cancelHostTimeout = function() {
        localClearTimeout(timeoutID);
        timeoutID = -1;
      };
    

    解析:
    React源码解析之requestHostCallback 中有涉及到,就是取消 B 执行调度任务

    三、advanceTimers
    作用:
    检查是否有不过期的任务,并把它们加入到新的调度队列中

    源码:

    //检查是否有不过期的任务,并把它们加入到新的调度队列中
    function advanceTimers(currentTime) {
      // Check for tasks that are no longer delayed and add them to the queue.
      //开始时间已经晚于当前时间了
      if (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime) {
        do {
          const task = firstDelayedTask;
          const next = task.next;
          //调度任务队列是一个环状的链表
          //说明只有一个过期任务,将其置为 null
          if (task === next) {
            firstDelayedTask = null;
          }
          //将当前的 task 挤掉
          else {
            firstDelayedTask = next;
            const previous = task.previous;
            previous.next = next;
            next.previous = previous;
          }
          //让 task 摆脱与旧的调度队列的依赖
          task.next = task.previous = null;
          //将 task 插入到新的调度队列中
          insertScheduledTask(task, task.expirationTime);
        } while (
          firstDelayedTask !== null &&
          firstDelayedTask.startTime <= currentTime
        );
      }
    }
    

    解析:
    (1) firstDelayedTask 表示过期的任务
    (2) 如果 过期任务存在,并且仍未执行,则将该 task 拿出
    (3) 调用insertScheduledTask将 task 插入到新的调度队列中

    四、insertScheduledTask
    作用:
    将 newTask 插入到新的调度队列中

    源码:

    //将 newTask 插入到新的调度队列中
    function insertScheduledTask(newTask, expirationTime) {
      // Insert the new task into the list, ordered first by its timeout, then by
      // insertion. So the new task is inserted after any other task the
      // same timeout
      if (firstTask === null) {
        // This is the first task in the list.
        firstTask = newTask.next = newTask.previous = newTask;
      } else {
        var next = null;
        var task = firstTask;
        //React对传进来的 callback 进行排序,
        // 优先级高的排在前面,优先级低的排在后面
        do {
          if (expirationTime < task.expirationTime) {
            // The new task times out before this one.
            next = task;
            break;
          }
          task = task.next;
        } while (task !== firstTask);
        //优先级最小的话
        if (next === null) {
          // No task with a later timeout was found, which means the new task has
          // the latest timeout in the list.
          next = firstTask;
        }
        //优先级最高的话
        else if (next === firstTask) {
          // The new task has the earliest expiration in the entire list.
          firstTask = newTask;
        }
        //插入 newTask
        var previous = next.previous;
        previous.next = next.previous = newTask;
        newTask.next = next;
        newTask.previous = previous;
      }
    }
    

    解析:
    这段比较简单,主要是链表的一些操作,逻辑就是:
    按照传进来的 task 的优先级高低排序,并插入到新的调度队列中

    五、flushTask
    作用:
    将调度任务从调度队列中拿出,并执行,之后将调度任务生出的子调度任务插入到其后

    源码:

    // 将调度任务从调度队列中拿出,并执行;
    // 将调度任务生出的子调度任务插入到其后
    function flushTask(task, currentTime) {
      // Remove the task from the list before calling the callback. That way the
      // list is in a consistent state even if the callback throws.
      // 将过期的任务在调度前从调度队列中移除,以让调度队列的任务均保持不过期(一致)的状态
      const next = task.next;
      // 如果当前队列中只有一个回调任务,则清空队列
      if (next === task) {
        // This is the only scheduled task. Clear the list.
        firstTask = null;
      }
      else {
        // Remove the task from its position in the list.
        //如果当前任务正好等于firstTask,则firstTask指向下一个回调任务
        if (task === firstTask) {
          firstTask = next;
        }
        // 将该 task 从调度队列中拿出来
        const previous = task.previous;
        previous.next = next;
        next.previous = previous;
      }
      // 单独拿出 task,以便安全地执行它
      task.next = task.previous = null;
    
      // Now it's safe to execute the task.
      var callback = task.callback;
      // 之前的调度优先级
      var previousPriorityLevel = currentPriorityLevel;
      // 之前的调度任务
      var previousTask = currentTask;
    
      // 当前任务
      currentPriorityLevel = task.priorityLevel;
      currentTask = task;
      // 回调任务返回的内容
      var continuationCallback;
      try {
        // 当前的回调任务是否超时,false 超时,true 没有
        var didUserCallbackTimeout = task.expirationTime <= currentTime;
        // 执行回调任务,返回的结果由 continuationCallback 保存
        continuationCallback = callback(didUserCallbackTimeout);
      } catch (error) {
        throw error;
      } finally {
        // 重置任务优先级和任务
        currentPriorityLevel = previousPriorityLevel;
        currentTask = previousTask;
      }
    
      // A callback may return a continuation. The continuation should be scheduled
      // with the same priority and expiration as the just-finished callback.
      // 调度任务可能会有返回的内容,如果返回的是一个 function,
      // 该 function 应该和刚刚执行的 callback 一样,有同样的优先级
      if (typeof continuationCallback === 'function') {
        var expirationTime = task.expirationTime;
        // 将回调任务的结果再拼成一个子回调任务
        var continuationTask = {
          callback: continuationCallback,
          priorityLevel: task.priorityLevel,
          startTime: task.startTime,
          expirationTime,
          next: null,
          previous: null,
        };
    
        // Insert the new callback into the list, sorted by its timeout. This is
        // almost the same as the code in `scheduleCallback`, except the callback
        // is inserted into the list *before* callbacks of equal timeout instead
        // of after.
    
        // 如果调度队列为空的话,将子回调任务插入调度队列
        if (firstTask === null) {
          // This is the first callback in the list.
          firstTask = continuationTask.next = continuationTask.previous = continuationTask;
        }
        //判断子回调任务的优先级
        else {
          var nextAfterContinuation = null;
          var t = firstTask;
          // 如果当前调度优先级小于 firstTask 的优先级的话,
          // 下一个要执行的调度任务就是 firstTask
          // ps:但是这个循环感觉不会执行,因为 var t = firstTask;
          do {
    
            if (expirationTime <= t.expirationTime) {
              // This task times out at or after the continuation. We will insert
              // the continuation *before* this task.
              nextAfterContinuation = t;
              break;
            }
            t = t.next;
          } while (t !== firstTask);
          if (nextAfterContinuation === null) {
            // No equal or lower priority task was found, which means the new task
            // is the lowest priority task in the list.
            //没有相同或更低的优先级的调度任务找到,意味着新任务就是最低优先级的任务
            nextAfterContinuation = firstTask;
          }
          //否则新任务是最高优先级的任务
          else if (nextAfterContinuation === firstTask) {
            // The new task is the highest priority task in the list.
            firstTask = continuationTask;
          }
          // 将子回调任务插入调度队列中
          const previous = nextAfterContinuation.previous;
          previous.next = nextAfterContinuation.previous = continuationTask;
          continuationTask.next = nextAfterContinuation;
          continuationTask.previous = previous;
        }
      }
    }
    

    解析:
    (1) 将 task 从调度队列中拿出
    (2) 执行该 task,返回的结果由continuationCallback保存
    (3) 如果continuationCallback返回的是一个function,将该回调任务的结果再拼成一个子回调任务
    (4) 将子回调任务插入调度队列中

    六、总结
    本文的源码逻辑不算复杂,但是需要熟悉链表的操作。

    GitHub:
    https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/scheduler/src/Scheduler.js


    (完)

    相关文章

      网友评论

          本文标题:React源码解析之flushWork

          本文链接:https://www.haomeiwen.com/subject/bgrmpctx.html