React源码解析之scheduleWork(下)
- 2019 年 10 月 6 日
- 笔记
八、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
觉得不错的话,在下方留言都是一种支持 (●'◡'●)ノ 。
(完)