前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之flushWork

React源码解析之flushWork

作者头像
进击的小进进
发布2019-10-23 19:22:18
5850
发布2019-10-23 19:22:18
举报

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

代码语言:javascript
复制
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 作用: 刷新调度队列,执行调度任务

源码:

代码语言:javascript
复制
//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 作用: 取消执行调度任务

源码:

代码语言:javascript
复制
  cancelHostTimeout = function() {
    localClearTimeout(timeoutID);
    timeoutID = -1;
  };

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

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

源码:

代码语言:javascript
复制
//检查是否有不过期的任务,并把它们加入到新的调度队列中
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 插入到新的调度队列中

源码:

代码语言:javascript
复制
//将 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 作用: 将调度任务从调度队列中拿出,并执行,之后将调度任务生出的子调度任务插入到其后

源码:

代码语言:javascript
复制
// 将调度任务从调度队列中拿出,并执行;
// 将调度任务生出的子调度任务插入到其后
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档