lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

reactScheduler #4

Open lz-lee opened 5 years ago

lz-lee commented 5 years ago

reactScheduler 异步任务调度

reactScheduler 核心功能

时间片概念

scheduleCallbackWithExpirationTime 异步任务调度

requestWork 中中如果判断是异步调度的方法就会执行 scheduleCallbackWithExpirationTime

// requestWork
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // ...
  if (expirationTime === Sync) { // 同步的调用 js 代码
    performSyncWork();
  } else { // 异步调度 独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
    scheduleCallbackWithExpirationTime(root, expirationTime); // 在 secheduler 文件夹下的单独模块
  }
}

function scheduleCallbackWithExpirationTime(
  root: FiberRoot,
  expirationTime: ExpirationTime,
) {
  // callbackExpirationTime 是上一次调度的任务优先级
  if (callbackExpirationTime !== NoWork) {
    // A callback is already scheduled. Check its expiration time (timeout).
    // 当前优先级比之前正在执行的优先级低就停止
    if (expirationTime > callbackExpirationTime) {
      // Existing callback has sufficient timeout. Exit.
      return;
    } else {
      //  当前优先级更高 则取消原来那个
      if (callbackID !== null) {
        // Existing callback has insufficient timeout. Cancel and schedule a
        // new one.
        cancelDeferredCallback(callbackID);
      }
    }
    // The request callback timer is already running. Don't start a new one.
  } else {
    startRequestCallbackTimer();
  }

  // 保存当前任务优先级
  callbackExpirationTime = expirationTime;
  // originalStartTimeMs 是 react 加载的最初时间, 记录当前时间差
  const currentMs = now() - originalStartTimeMs;
  // expirationTime(到期时间为将来的某个时间)转化成 ms
  const expirationTimeMs = expirationTimeToMs(expirationTime);
  // 当前任务的过期时间, 到期时间减去当前任务开始调度的时间差,则为过期时间,如果timeout < 0 ,则表示任务过期,需要强制更新
  const timeout = expirationTimeMs - currentMs;
  // 依赖 Scheduler 模块返回的id用来cancel
  callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
}

scheduleCallback

scheduler 模块下 unstable_scheduleCallback 函数

// ReactDOMHostConfig.js

export {
  unstable_now as now,
  unstable_scheduleCallback as scheduleDeferredCallback,
  unstable_cancelCallback as cancelDeferredCallback,
} from 'scheduler';
// callback -> performAsyncWork, deprecated_options -> { timeout }
function unstable_scheduleCallback(callback, deprecated_options) {
  // 即 Date.now()
  var startTime =
    currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();

  var expirationTime;
  if (
    typeof deprecated_options === 'object' &&
    deprecated_options !== null &&
    typeof deprecated_options.timeout === 'number'
  ) {
    // expirationTime 的逻辑将来可能全部移入到 Scheduler 包中, 目前只会进入这个判断
    // FIXME: Remove this branch once we lift expiration times out of React.
    expirationTime = startTime + deprecated_options.timeout;
  } else {
    switch (currentPriorityLevel) {
      case ImmediatePriority:
        expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
        break;
      case UserBlockingPriority:
        expirationTime = startTime + USER_BLOCKING_PRIORITY;
        break;
      case IdlePriority:
        expirationTime = startTime + IDLE_PRIORITY;
        break;
      case NormalPriority:
      default:
        expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
    }
  }

  var newNode = {
    // performAsyncWork
    callback,
    // 目前用不到
    priorityLevel: currentPriorityLevel,
    // timeout + now()
    expirationTime,
    // 存储链表结构
    next: null,
    previous: null,
  };

  // firstCallbackNode 是用来维护的双向链表的头部
  if (firstCallbackNode === null) {
    // This is the first callback in the list.
    firstCallbackNode = newNode.next = newNode.previous = newNode;
    // firstCallbackNode 改变需要调用  进入调度过程
    ensureHostCallbackIsScheduled();
  } else {
    // 如果有一个/多个 node 存在链表结构里
    // next表示下一个要执行的node
    var next = null;
    // 原来的
    var node = firstCallbackNode;
    do {
      // 根据 expirationTime 排序,把优先级高的放在前面,每次都跟 firstCallbackNode 比较
      if (node.expirationTime > expirationTime) {
        // The new callback expires before this one.
        // 表示新的node的优先级高于原来的,将原来的(firstCallbackNode)赋值给 next, 退出循环
        next = node;
        break;
      }
      node = node.next;
    } while (node !== firstCallbackNode);

    // 如果 nex t为null,说明 node.expirationTime < expirationTime, 即新的node的优先级是最小的
    if (next === null) {
      // No callback with a later expiration was found, which means the new
      // callback has the latest expiration in the list.
      // next为原来的node
      next = firstCallbackNode;
    // 表示新的node的优先级 高于原来node的优先级,
    } else if (next === firstCallbackNode) {
      // The new callback has the earliest expiration in the entire list.
      // 重新将 firstCallbackNode 赋值为新的node,即保证firstCallbackNode 为优先级最高的那个node
      firstCallbackNode = newNode;
      // firstCallbackNode 改变需要调用  进入调度过程
      ensureHostCallbackIsScheduled();
    }

    // 原来 指向firstCallBakcNode.previous 和 指向firstCallBakcNode.next 都指向自己
    // previous、next 都是指向firstCallBakcNode
    var previous = next.previous;
    // 现在将 都是指向firstCallBakcNode.previous 和 都是指向firstCallBakcNode.next 指向 newNode
    previous.next = next.previous = newNode;

    // 将 newNode.previous 和newNode.next 指向firstCallBakcNode, 形成环状双向链表结构
    newNode.next = next;
    newNode.previous = previous;

  }

  return newNode;
}

ensureHostCallbackIsScheduled

function ensureHostCallbackIsScheduled() {
  // 表示已经有一个 callbackNode 在调用了
  if (isExecutingCallback) {
    // Don't schedule work yet; wait until the next time we yield.
    return;
  }
  // Schedule the host callback using the earliest expiration in the list.
  var expirationTime = firstCallbackNode.expirationTime;
  // 判断这个 hostCallback 有没有进入调度
  if (!isHostCallbackScheduled) {
    isHostCallbackScheduled = true;
  } else {
    // Cancel the existing host callback.
    cancelHostCallback();
  }
  requestHostCallback(flushWork, expirationTime);
}

requestHostCallback

// callback 为 flushWork 函数
requestHostCallback = function(callback, absoluteTimeout) {
    scheduledHostCallback = callback;
    timeoutTime = absoluteTimeout;
    // absoluteTimeout < 0 已经超时,直接强制执行
    if (isFlushingHostCallback || absoluteTimeout < 0) {
      // Don't wait for the next frame. Continue working ASAP, in a new event.
      window.postMessage(messageKey, '*');
      // isAnimationFrameScheduled = false 表示还没进入调度循环
    } else if (!isAnimationFrameScheduled) {
      // If rAF didn't already schedule one, we need to schedule a frame.
      // TODO: If this rAF doesn't materialize because the browser throttles, we
      // might want to still have setTimeout trigger rIC as a backup to ensure
      // that we keep performing work.
      isAnimationFrameScheduled = true;
      // 进入调度, 竞争调用 animationTick
      requestAnimationFrameWithTimeout(animationTick);
    }
  };

requestAnimationFrameWithTimeout

var ANIMATION_FRAME_TIMEOUT = 100;
var rAFID;
var rAFTimeoutID;
var requestAnimationFrameWithTimeout = function(callback) {
  // schedule rAF and also a setTimeout
  // requestAnimationFrame 执行则取消 setTimeout
  rAFID = localRequestAnimationFrame(function(timestamp) {
    // cancel the setTimeout
    localClearTimeout(rAFTimeoutID);
    callback(timestamp);
  });
  // 100ms 内 requestAnimationFrame 内的 callback 还未执行,则取消requestAnimationFrame,
  rAFTimeoutID = localSetTimeout(function() {
    // cancel the requestAnimationFrame
    localCancelAnimationFrame(rAFID);
    // 传入当前时间
    callback(getCurrentTime());
  }, ANIMATION_FRAME_TIMEOUT);
};

animationTick

  var frameDeadline = 0;
  // We start out assuming that we run at 30fps but then the heuristic tracking
  // will adjust this value to a faster fps if we get more frequent animation
  // frames.
  // 假设以 30fps 运行,然后进行跟踪,如果获得更频繁的帧,则会将此值调整为更快的fps
  var previousFrameTime = 33;
  var activeFrameTime = 33;

var animationTick = function(rafTime) {
    if (scheduledHostCallback !== null) {
      // 立马请求下一帧调用自己,不停的调用,队列里有很多 callback
      requestAnimationFrameWithTimeout(animationTick);
    } else {
      // No pending work. Exit.
      // 没有方法要被调度,返回
      isAnimationFrameScheduled = false;
      return;
    }
    //  rafTime 是调用的当前时间,frameDeadline 为0,activeFrameTime 为 33,即保持浏览器一秒 30帧,每一帧的执行一帧完整的时间
    // 用来计算下一帧可以执行的时间是多少,即nextFrameTime
    var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
    // 下一个方法调用进来 下一帧重新计算nextFrameTime,rafTime - frameDeadline 如果小于 0 ,说明浏览器的刷新频率要大于 30hz,帧时间是要小于 33ms的
    // 如果连续两次帧的调用计算出来的时间是小于 33ms,就设置帧时间变小,
    // 主要针对不同平台的刷新频率的问题,比如 vr 120帧,根据平台的刷新频率设置 activeFrameTime, 1000 / 120 -> 8
    if (
      nextFrameTime < activeFrameTime &&
      previousFrameTime < activeFrameTime
    ) {
      if (nextFrameTime < 8) {
        // Defensive coding. We don't support higher frame rates than 120hz.
        // If the calculated frame time gets lower than 8, it is probably a bug.
        // 每帧最小运行时间
        nextFrameTime = 8;
      }
      // If one frame goes long, then the next one can be short to catch up.
      // If two frames are short in a row, then that's an indication that we
      // actually have a higher frame rate than what we're currently optimizing.
      // We adjust our heuristic dynamically accordingly. For example, if we're
      // running on 120hz display or 90hz VR display.
      // Take the max of the two in case one of them was an anomaly due to
      // missed frame deadlines.
      // 通过前后帧时间的判断,来判断平台的刷新频率,然后更新 activeFrameTime, 来达到减少React运行占用时间的目的
      activeFrameTime =
        nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
    } else {
      // 前一帧运行时间和下一帧运行时间相同
      previousFrameTime = nextFrameTime;
    }
    // 第一个 frameDeadline
    frameDeadline = rafTime + activeFrameTime;
    // isMessageEventScheduled 会在 cancelHostCallback 和 idleTick 里都会设置为 false
    if (!isMessageEventScheduled) {
      isMessageEventScheduled = true;
      // 通过 requestAnimationFrame 调用完 callback 后立马会进入浏览器动画更新的设定,往任务队列里插入react任务来模拟 requestIdleCallback 方法
      // postMessage属于 macrotask,等待浏览器执行完再执行队列里的任务,即使每一帧的时间为33ms,但等到 message 接收时,实际留给react的时间没有 33ms
      // 给任务队列插入 react 任务,等浏览器执行完自己的任务再执行这里队列里的
      window.postMessage(messageKey, '*');
    }
  };

idleTick

// 接受 react 任务
window.addEventListener('message', idleTick, false);

var idleTick = function(event) {
    // 判断 key 和 判断 message 源
    if (event.source !== window || event.data !== messageKey) {
      return;
    }

    isMessageEventScheduled = false;

    // 先赋值 再重置,
    // scheduledHostCallback -> flushWork
    // timeoutTime -> firstCallbackNode.expirationTime 到期时间
    var prevScheduledCallback = scheduledHostCallback;
    var prevTimeoutTime = timeoutTime;
    scheduledHostCallback = null;
    timeoutTime = -1;

    var currentTime = getCurrentTime();

    var didTimeout = false;
    // 表示浏览器动画或用户反馈操作超过 33ms, 把这一帧的时间用完了,react 没有时间去更新了
    if (frameDeadline - currentTime <= 0) {
      // There's no time left in this idle period. Check if the callback has
      // a timeout and whether it's been exceeded.
      // 表示当前任务也已经过期
      if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
        // Exceeded the timeout. Invoke the callback even though there's no
        // time left.
        // 标识已过期,准备强制更新
        didTimeout = true;
      } else {
        // No timeout.
        // 没有过期,且 isAnimationFrameScheduled 为 false 的情况,则直接调用 requestAnimationFrameWithTimeout
        // 在 animationTick 函数中 判断 scheduledHostCallback === null 时才会赋值其为false,
        // 而 scheduledHostCallback === null 只有在 cancelHostCallback 函数 和 idleTick 函数中执行
        if (!isAnimationFrameScheduled) {
          // Schedule another animation callback so we retry later.
          // 调度另一个动画回调,以便我们稍后重试
          isAnimationFrameScheduled = true;
          requestAnimationFrameWithTimeout(animationTick);
        }
        // Exit without invoking the callback.
        // 没有过期,则退出而不执行callback,将scheduledHostCallback、tiemoutTime 恢复原来值,直接return
        scheduledHostCallback = prevScheduledCallback;
        timeoutTime = prevTimeoutTime;
        return;
      }
    }

    if (prevScheduledCallback !== null) {
      // 正在调用这个 callback
      isFlushingHostCallback = true;
      try {
        // 根据 didTimeout 判断 是否强制执行更新
        prevScheduledCallback(didTimeout);
      } finally {
        isFlushingHostCallback = false;
      }
    }
  };

flushWork

var deadlineObject = {
  timeRemaining,
  didTimeout: false,
};
// 默认 hasNativePerformanceNow = true
var timeRemaining = function() {
    // ...
    // We assume that if we have a performance timer that the rAF callback
    // gets a performance timer value. Not sure if this is always true.
    // frameDeadline - now() > 0  这一帧的渲染剩余多少时间, 在 shouldYield 中使用并判断
    // 模拟传给 requestIdleCallback方法的回调函数的 IdleDeadline 参数 https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline 表示当前闲置周期的预估剩余毫秒数

    var remaining = getFrameDeadline() - performance.now();
    return remaining > 0 ? remaining : 0;
}

// didTimout -> firstCallbackNode 是否已经超时的判断
function flushWork(didTimeout) {
  // 开始调用 callback -> ensureHostCallbackIsScheduled 函数会直接return
  isExecutingCallback = true;
  deadlineObject.didTimeout = didTimeout;
  try {
    // firstCallbackNode的任务已经过期
    if (didTimeout) {
      // Flush all the expired callbacks without yielding.
      // 循环
      while (firstCallbackNode !== null) {
        // Read the current time. Flush all the callbacks that expire at or
        // earlier than that time. Then read the current time again and repeat.
        // This optimizes for as few performance.now calls as possible.
        var currentTime = getCurrentTime();
        // 第一个 expirationTime 肯定小于 currentTime, 为过期任务
        if (firstCallbackNode.expirationTime <= currentTime) {
          // 循环执行callback链表,直到第一个没有过期的任务为止, 已经过期的任务都强制输出
          do {
            // flushFirstCallback 会将 firstCallbackNode.next 赋值给 firstCallbackNode
            flushFirstCallback();
          } while (
            // firstCallbackNode 变成下一个,循环判断链表任务是否过期
            firstCallbackNode !== null &&
            firstCallbackNode.expirationTime <= currentTime
          );
          // 已经过期的任务都输出了,下一次判断if (firstCallbackNode.expirationTime <= currentTime) 为false,直接break
          continue;
        }
        break;
      }
    } else {
      // Keep flushing callbacks until we run out of time in the frame.
      // 任务没有过期, 继续刷新回调,直到帧中的时间不足为止。
      if (firstCallbackNode !== null) {
        do {
          flushFirstCallback();
        } while (
          // 帧还有时间空闲(还有剩余时间)才执行 callback
          firstCallbackNode !== null &&
          getFrameDeadline() - getCurrentTime() > 0
        );
      }
    }
  } finally {
    isExecutingCallback = false;
    if (firstCallbackNode !== null) {
      // There's still work remaining. Request another callback.
      // 还剩有 firsrCallbackNode 时,调用ensureHostCallbackIsScheduled ,此时 isHostCallbackScheduled 为 true,则会执行 cancelHostCallback 重置所有的调度常量,将 scheduledHostCallback重置为null,避免在 animationTick 中将老的 callback 重复执行一边
      ensureHostCallbackIsScheduled();
    } else {
      isHostCallbackScheduled = false;
    }
    // Before exiting, flush all the immediate work that was scheduled.
    // firstCallbackNode.priorityLevel === currentPriorityLevel 这个函数不会执行
    flushImmediateWork();
  }
}

flushFirstCallback

function flushFirstCallback() {
  var flushedNode = firstCallbackNode;

  // Remove the node from the list before calling the callback. That way the
  // list is in a consistent state even if the callback throws.
  var next = firstCallbackNode.next;
  // 表示只有一个 callbackNode
  if (firstCallbackNode === next) {
    // This is the last callback in the list.
    firstCallbackNode = null;
    next = null;
  } else {
    // 替换 firstCallbackNode 的操作
    var lastCallbackNode = firstCallbackNode.previous;
    // 将 firstCallbackNode 指向 firstCallbackNode.next
    firstCallbackNode = lastCallbackNode.next = next;
    next.previous = lastCallbackNode;
  }

  // 清空老 firstCallbackNode 的 next 和 previous
  flushedNode.next = flushedNode.previous = null;

  // Now it's safe to call the callback.
  var callback = flushedNode.callback;
  var expirationTime = flushedNode.expirationTime;
  var priorityLevel = flushedNode.priorityLevel;
  var previousPriorityLevel = currentPriorityLevel;
  var previousExpirationTime = currentExpirationTime;
  currentPriorityLevel = priorityLevel;
  currentExpirationTime = expirationTime;
  var continuationCallback;
  try {
    // 执行 callback
    continuationCallback = callback(deadlineObject);
  } finally {
    currentPriorityLevel = previousPriorityLevel;
    currentExpirationTime = previousExpirationTime;
  }

  // performAsyncWork 没有返回则下面不会执行
   if (typeof continuationCallback === 'function') {
     // ....
   }
}