美文网首页
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