// 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
检查是否有 callbackNode 在执行,否则停止执行
判断 hostCallback 是否在调度,已经调度就取消
执行 requestHostCallback
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);
}
};
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, '*');
}
};
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
将 firstCallbackNode 指向它的next处理链表表头,执行callback
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') {
// ....
}
}
reactScheduler 异步任务调度
reactScheduler 核心功能
时间片概念
scheduleCallbackWithExpirationTime 异步任务调度
在
requestWork
中中如果判断是异步调度的方法就会执行 scheduleCallbackWithExpirationTimescheduleCallback
scheduler
模块下unstable_scheduleCallback
函数firstCallbackNode
是维护的双向链表结构的头部ensureHostCallbackIsScheduled
callbackNode
在执行,否则停止执行hostCallback
是否在调度,已经调度就取消requestHostCallback
requestHostCallback
requestAnimationFrameWithTimeout
animationTick
idleTick
flushWork
flushFirstCallback