前言:
先看一下flushWork
在 React源码解析之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) 如果能执行到此步,意味着可以执行调度任务,设isPerformingWork
为true
(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) 将子回调任务插入调度队列中
六、总结
本文的源码逻辑不算复杂,但是需要熟悉链表的操作。
(完)
网友评论