八、scheduleCallbackForRoot()
作用:
在render()
之后,立即执行调度任务
源码:
// Use this function, along with runRootCallback, to ensure that only a single
// callback per root is scheduled. It's still possible to call renderRoot
// directly, but scheduling via this function helps avoid excessive callbacks.
// It works by storing the callback node and expiration time on the root. When a
// new callback comes in, it compares the expiration time to determine if it
// should cancel the previous one. It also relies on commitRoot scheduling a
// callback to render the next level, because that means we don't need a
// separate callback per expiration time.
//同步调用callback
//流程是在root上存取callback和expirationTime,
// 当新的callback调用时,比较更新expirationTime
function scheduleCallbackForRoot(
root: FiberRoot,
priorityLevel: ReactPriorityLevel,
expirationTime: ExpirationTime,
) {
//获取root的回调过期时间
const existingCallbackExpirationTime = root.callbackExpirationTime;
//更新root的回调过期时间
if (existingCallbackExpirationTime < expirationTime) {
// New callback has higher priority than the existing one.
//当新的expirationTime比已存在的callback的expirationTime优先级更高的时候
const existingCallbackNode = root.callbackNode;
if (existingCallbackNode !== null) {
//取消已存在的callback(打断)
//将已存在的callback节点从链表中移除
cancelCallback(existingCallbackNode);
}
//更新callbackExpirationTime
root.callbackExpirationTime = expirationTime;
//如果是同步任务
if (expirationTime === Sync) {
// Sync React callbacks are scheduled on a special internal queue
//在临时队列中同步被调度的callback
root.callbackNode = scheduleSyncCallback(
runRootCallback.bind(
null,
root,
renderRoot.bind(null, root, expirationTime),
),
);
} else {
let options = null;
if (expirationTime !== Never) {
//(Sync-2 - expirationTime) * 10-now()
let timeout = expirationTimeToMs(expirationTime) - now();
options = {timeout};
}
//callbackNode即经过处理包装的新task
root.callbackNode = scheduleCallback(
priorityLevel,
//bind()的意思是绑定this,xx.bind(y)()这样才算执行
runRootCallback.bind(
null,
root,
renderRoot.bind(null, root, expirationTime),
),
options,
);
if (
enableUserTimingAPI &&
expirationTime !== Sync &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Scheduled an async callback, and we're not already working. Add an
// entry to the flamegraph that shows we're waiting for a callback
// to fire.
//开始调度callback的标志
startRequestCallbackTimer();
}
}
}
// Associate the current interactions with this new root+priority.
//跟踪这些update,并计数、检测它们是否会报错
schedulePendingInteractions(root, expirationTime);
}
解析: Fiber机制可以为每一个update任务进行优先级排序,并且可以记录调度到了哪里(schedulePendingInteractions())
同时,还可以中断正在执行的任务,优先执行优先级比当前高的任务(scheduleCallbackForRoot()
),之后,还可以继续之前中断的任务,而React16 之前调用setState()
,必须等待setState
的update
队列全部调度完,才能进行之后的操作。
一起看下scheduleCallbackForRoot()
做了什么:
(1)当新的scheduleCallback
的优先级更高时,中断当前任务cancelCallback(existingCallbackNode)
(2)如果是同步任务,则在临时队列中进行调度
(3)如果是异步任务,则更新调度队列的状态
(4)设置开始调度的时间节点
(5)跟踪调度的任务
具体讲解,请耐心往下看
九、cancelCallback() 作用: 中断正在执行的调度任务
源码:
const {
unstable_cancelCallback: Scheduler_cancelCallback,
} = Scheduler;
//从链表中移除task节点
function unstable_cancelCallback(task) {
//获取callbackNode的next节点
var next = task.next;
//由于链表是双向循环链表,一旦next是null则证明该节点已不存在于链表中
if (next === null) {
// Already cancelled.
return;
}
//自己等于自己,说明链表中就这一个callback节点
//firstTask/firstDelayedTask应该是类似游标的概念,即正要执行的节点
if (task === next) {
//置为null,即删除callback节点
//重置firstTask/firstDelayedTask
if (task === firstTask) {
firstTask = null;
} else if (task === firstDelayedTask) {
firstDelayedTask = null;
}
} else {
//将firstTask/firstDelayedTask指向下一节点
if (task === firstTask) {
firstTask = next;
} else if (task === firstDelayedTask) {
firstDelayedTask = next;
}
var previous = task.previous;
//熟悉的链表操作,删除已存在的callbackNode
previous.next = next;
next.previous = previous;
}
task.next = task.previous = null;
}
解析:
操作schedule
链表,将正要执行的callback
“移除”,将游标指向下一个调度任务
十、scheduleSyncCallback()
作用:
如果是同步任务的话,则执行scheduleSyncCallback()
,将调度任务入队,并返回入队后的临时队列
源码:
//入队callback,并返回临时的队列
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
//在下次调度或调用 刷新同步回调队列 的时候刷新callback队列
//如果同步队列为空的话,则初始化同步队列,
//并在下次调度的一开始就刷新队列
if (syncQueue === null) {
syncQueue = [callback];
// Flush the queue in the next tick, at the earliest.
immediateQueueCallbackNode = Scheduler_scheduleCallback(
//赋予调度立即执行的高权限
Scheduler_ImmediatePriority,
flushSyncCallbackQueueImpl,
);
}
//如果同步队列不为空的话,则将callback入队
else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
//在入队的时候,不必去调度callback,因为在创建队列的时候就已经调度了
syncQueue.push(callback);
}
//fake我认为是临时队列的意思
return fakeCallbackNode;
}
解析:
(1)当同步队列为空
调用Scheduler_scheduleCallback()
,将该callback
任务入队,并把该callback
包装成newTask
,赋给root.callbackNode
Scheduler_scheduleCallback():
const {
unstable_scheduleCallback: Scheduler_scheduleCallback,
} = Scheduler;
//返回经过包装处理的task
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
var timeout;
//更新startTime(默认是现在)和timeout(默认5s)
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
timeout =
typeof options.timeout === 'number'
? options.timeout
: timeoutForPriorityLevel(priorityLevel);
} else {
// Times out immediately
// var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
// var USER_BLOCKING_PRIORITY = 250;
//普通优先级的过期时间是5s
// var NORMAL_PRIORITY_TIMEOUT = 5000;
//低优先级的过期时间是10s
// var LOW_PRIORITY_TIMEOUT = 10000;
timeout = timeoutForPriorityLevel(priorityLevel);
startTime = currentTime;
}
//过期时间是当前时间+5s,也就是默认是5s后,react进行更新
var expirationTime = startTime + timeout;
//封装成新的任务
var newTask = {
callback,
priorityLevel,
startTime,
expirationTime,
next: null,
previous: null,
};
//如果开始调度的时间已经错过了
if (startTime > currentTime) {
// This is a delayed task.
//将延期的callback插入到延期队列中
insertDelayedTask(newTask, startTime);
//如果调度队列的头任务没有,并且延迟调度队列的头任务正好是新任务,
//说明所有任务均延期,并且此时的任务是第一个延期任务
if (firstTask === null && firstDelayedTask === newTask) {
// All tasks are delayed, and this is the task with the earliest delay.
//如果延迟调度开始的flag为true,则取消定时的时间
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
}
//否则设为true
else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
}
//没有延期的话,则按计划插入task
else {
insertScheduledTask(newTask, expirationTime);
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
//更新调度执行的标志
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
//返回经过包装处理的task
return newTask;
}
当有新的update时,React 默认是 5s 后进行更新(直观地说,就是你更新了开发代码,过 5s,也可以说是最迟过了 5s,网页更新)
Scheduler_scheduleCallback()
的作用是:
① 确定当前时间startTime
和延迟更新时间timeout
② 新建newTask
对象(包含callback
、expirationTime
)
③ 如果是延迟调度的话,将newTask
放入【延迟调度队列】
④ 如果是正常调度的话,将newTask
放入【正常调度队列】
⑤ 返回包装的newTask
(2)当同步队列不为空
将该callback
入队
scheduleSyncCallback()
最终返回临时回调节点。
十一、scheduleCallback()
作用:
如果是异步任务的话,则执行scheduleCallback()
,对callback
进行包装处理,并更新调度队列的状态
源码:
//对callback进行包装处理,并更新调度队列的状态
export function scheduleCallback(
reactPriorityLevel: ReactPriorityLevel,
callback: SchedulerCallback,
options: SchedulerCallbackOptions | void | null,
) {
//获取调度的优先级
const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
解析:
和十
一样,调用Scheduler_scheduleCallback()
,将该callback
任务入队,并把该callback
包装成newTask
,赋给root.callbackNode
tips:
func.bind(xx)
的意思是func
里的this
绑定的是xx
,
也就是说 是xx
调用func
方法
注意!func.bind(xx)
这仅仅是绑定,而不是调用!
func.bind(xx)()
这样才算是xx
调用func
方法!
至此,scheduleCallbackForRoot()
已分析完毕(八到十一)
我们讲到这里了:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
}else{
/*八到十一讲解的内容*/
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
//当前没有update时
if (executionContext === NoContext) {
//刷新同步任务队列
flushSyncCallbackQueue();
}
}
}
}
十二、flushSyncCallbackQueue() 作用: 更新同步任务队列的状态
源码:
//刷新同步任务队列
export function flushSyncCallbackQueue() {
//如果即时节点存在则中断当前节点任务,从链表中移除task节点
if (immediateQueueCallbackNode !== null) {
Scheduler_cancelCallback(immediateQueueCallbackNode);
}
//更新同步队列
flushSyncCallbackQueueImpl();
}
flushSyncCallbackQueueImpl():
//更新同步队列
function flushSyncCallbackQueueImpl() {
//如果同步队列未更新过并且同步队列不为空
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
//防止重复执行,相当于一把锁
isFlushingSyncQueue = true;
let i = 0;
try {
const isSync = true;
const queue = syncQueue;
//遍历同步队列,并更新刷新的状态isSync=true
runWithPriority(ImmediatePriority, () => {
for (; i < queue.length; i++) {
let callback = queue[i];
do {
callback = callback(isSync);
} while (callback !== null);
}
});
//遍历结束后置为null
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
Scheduler_scheduleCallback(
Scheduler_ImmediatePriority,
flushSyncCallbackQueue,
);
throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}
解析:
当前调度的任务被中断时,先从链表中“移除”当前节点,并调用flushSyncCallbackQueueImpl ()
任务更新同步队列
循环遍历syncQueue
,并更新节点的isSync
状态(isSync=true)
然后到这里:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
}else{
/*八到十一讲解的内容*/
scheduleCallbackForRoot(root, ImmediatePriority, Sync);
if (executionContext === NoContext) {
//十二讲的内容
flushSyncCallbackQueue();
}
}
}
//如果是异步任务的话,则立即执行调度任务
//对应if (expirationTime === Sync)
else {
scheduleCallbackForRoot(root, priorityLevel, expirationTime);
}
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
// 只有在用户阻止优先级或更高优先级的更新才被视为离散,即使在离散事件中也是如此
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
//这是离散事件的结果。 跟踪每个根的最低优先级离散更新,以便我们可以在需要时尽早清除它们。
//如果rootsWithPendingDiscreteUpdates为null,则初始化它
if (rootsWithPendingDiscreteUpdates === null) {
//key是root,value是expirationTime
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
//获取最新的DiscreteTime
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
//更新DiscreteTime
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
scheduleUpdateOnFiber()的最后这一段没看懂是什么意思,猜测是调度结束之前,更新离散时间。
十三、scheduleWork流程图
GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js
觉得不错的话,在下方留言都是一种支持 (●'◡'●)ノ 。
(完)