react源碼層分析協(xié)調(diào)與調(diào)度
requestEventTime
其實在React
執(zhí)行過程中,會有數(shù)不清的任務要去執(zhí)行,但是他們會有一個優(yōu)先級的判定,假如兩個事件的優(yōu)先級一樣,那么React
是怎么去判定他們兩誰先執(zhí)行呢?
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js export function requestEventTime() { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // We're inside React, so it's fine to read the actual time. // react事件正在執(zhí)行 // executionContext // RenderContext 正在計算 // CommitContext 正在提交 // export const NoContext = /* */ 0b0000000; // const BatchedContext = /* */ 0b0000001; // const EventContext = /* */ 0b0000010; // const DiscreteEventContext = /* */ 0b0000100; // const LegacyUnbatchedContext = /* */ 0b0001000; // const RenderContext = /* */ 0b0010000; // const CommitContext = /* */ 0b0100000; // export const RetryAfterError = /* */ 0b1000000; return now(); } // 沒有在react事件執(zhí)行 NoTimestamp === -1 if (currentEventTime !== NoTimestamp) { // 瀏覽器事件正在執(zhí)行,返回上次的 currentEventTime return currentEventTime; } // 重新計算currentEventTime,當執(zhí)行被中斷后 currentEventTime = now(); return currentEventTime; }
RenderContext
與CommitContext
表示正在計算更新和正在提交更新,返回now()
。- 如果是瀏覽器事件正在執(zhí)行中,返回上一次的
currentEventTime
。 - 如果終止或者中斷react任務執(zhí)行的時候,則重新獲取執(zhí)行時間
now(
)。 - 獲取的時間越小,則執(zhí)行的優(yōu)先級越高。
now()
并不是單純的new Date()
,而是判定兩次更新任務的時間是否小于10ms
,來決定是否復用上一次的更新時間Scheduler_now
的。
export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
其實各位猜想一下,對于10ms
級別的任務間隙時間,幾乎是可以忽略不計的,那么這里就可以視為同樣的任務,不需要有很大的性能開銷,有利于批量更新。
requestUpdateLane
requestEventTime位每一個需要執(zhí)行的任務打上了觸發(fā)更新時間標簽,那么任務的優(yōu)先級還需要進一步的確立,requestUpdateLane就是用來獲取每一個任務執(zhí)行的優(yōu)先級的。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js export function requestUpdateLane(fiber: Fiber): Lane { // Special cases const mode = fiber.mode; if ((mode & BlockingMode) === NoMode) { return (SyncLane: Lane); } else if ((mode & ConcurrentMode) === NoMode) { return getCurrentPriorityLevel() === ImmediateSchedulerPriority ? (SyncLane: Lane) : (SyncBatchedLane: Lane); } else if ( !deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes ) { // This is a render phase update. These are not officially supported. The // old behavior is to give this the same "thread" (expiration time) as // whatever is currently rendering. So if you call `setState` on a component // that happens later in the same render, it will flush. Ideally, we want to // remove the special case and treat them as if they came from an // interleaved event. Regardless, this pattern is not officially supported. // This behavior is only a fallback. The flag only exists until we can roll // out the setState warning, since existing code might accidentally rely on // the current behavior. return pickArbitraryLane(workInProgressRootRenderLanes); } // The algorithm for assigning an update to a lane should be stable for all // updates at the same priority within the same event. To do this, the inputs // to the algorithm must be the same. For example, we use the `renderLanes` // to avoid choosing a lane that is already in the middle of rendering. // // However, the "included" lanes could be mutated in between updates in the // same event, like if you perform an update inside `flushSync`. Or any other // code path that might call `prepareFreshStack`. // // The trick we use is to cache the first of each of these inputs within an // event. Then reset the cached values once we can be sure the event is over. // Our heuristic for that is whenever we enter a concurrent work loop. // // We'll do the same for `currentEventPendingLanes` below. if (currentEventWipLanes === NoLanes) { currentEventWipLanes = workInProgressRootIncludedLanes; } const isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { if (currentEventPendingLanes !== NoLanes) { currentEventPendingLanes = mostRecentlyUpdatedRoot !== null ? mostRecentlyUpdatedRoot.pendingLanes : NoLanes; } return findTransitionLane(currentEventWipLanes, currentEventPendingLanes); } // TODO: Remove this dependency on the Scheduler priority. // To do that, we're replacing it with an update lane priority. // 獲取執(zhí)行任務的優(yōu)先級,便于調(diào)度 const schedulerPriority = getCurrentPriorityLevel(); // The old behavior was using the priority level of the Scheduler. // This couples React to the Scheduler internals, so we're replacing it // with the currentUpdateLanePriority above. As an example of how this // could be problematic, if we're not inside `Scheduler.runWithPriority`, // then we'll get the priority of the current running Scheduler task, // which is probably not what we want. let lane; if ( // TODO: Temporary. We're removing the concept of discrete updates. (executionContext & DiscreteEventContext) !== NoContext && // 用戶block的類型事件 schedulerPriority === UserBlockingSchedulerPriority ) { // 通過findUpdateLane函數(shù)重新計算lane lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes); } else { // 根據(jù)優(yōu)先級計算法則計算lane const schedulerLanePriority = schedulerPriorityToLanePriority( schedulerPriority, ); if (decoupleUpdatePriorityFromScheduler) { // In the new strategy, we will track the current update lane priority // inside React and use that priority to select a lane for this update. // For now, we're just logging when they're different so we can assess. const currentUpdateLanePriority = getCurrentUpdateLanePriority(); if ( schedulerLanePriority !== currentUpdateLanePriority && currentUpdateLanePriority !== NoLanePriority ) { if (__DEV__) { console.error( 'Expected current scheduler lane priority %s to match current update lane priority %s', schedulerLanePriority, currentUpdateLanePriority, ); } } } // 根據(jù)計算得到的 schedulerLanePriority,計算更新的優(yōu)先級 lane lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes); } return lane; }
- 通過
getCurrentPriorityLevel
獲得所有執(zhí)行任務的調(diào)度優(yōu)先級schedulerPriority
。 - 通過
findUpdateLane
計算lane
,作為更新中的優(yōu)先級。
findUpdateLane
export function findUpdateLane( lanePriority: LanePriority, wipLanes: Lanes, ): Lane { switch (lanePriority) { case NoLanePriority: break; case SyncLanePriority: return SyncLane; case SyncBatchedLanePriority: return SyncBatchedLane; case InputDiscreteLanePriority: { const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes); if (lane === NoLane) { // Shift to the next priority level return findUpdateLane(InputContinuousLanePriority, wipLanes); } return lane; } case InputContinuousLanePriority: { const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes); if (lane === NoLane) { // Shift to the next priority level return findUpdateLane(DefaultLanePriority, wipLanes); } return lane; } case DefaultLanePriority: { let lane = pickArbitraryLane(DefaultLanes & ~wipLanes); if (lane === NoLane) { // If all the default lanes are already being worked on, look for a // lane in the transition range. lane = pickArbitraryLane(TransitionLanes & ~wipLanes); if (lane === NoLane) { // All the transition lanes are taken, too. This should be very // rare, but as a last resort, pick a default lane. This will have // the effect of interrupting the current work-in-progress render. lane = pickArbitraryLane(DefaultLanes); } } return lane; } case TransitionPriority: // Should be handled by findTransitionLane instead case RetryLanePriority: // Should be handled by findRetryLane instead break; case IdleLanePriority: let lane = pickArbitraryLane(IdleLanes & ~wipLanes); if (lane === NoLane) { lane = pickArbitraryLane(IdleLanes); } return lane; default: // The remaining priorities are not valid for updates break; } invariant( false, 'Invalid update priority: %s. This is a bug in React.', lanePriority, ); }
相關參考視頻講解:進入學習
lanePriority LanePriority
export opaque type LanePriority = | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17; export opaque type Lanes = number; export opaque type Lane = number; export opaque type LaneMap<T> = Array<T>; import { ImmediatePriority as ImmediateSchedulerPriority, UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, LowPriority as LowSchedulerPriority, IdlePriority as IdleSchedulerPriority, NoPriority as NoSchedulerPriority, } from './SchedulerWithReactIntegration.new'; // 同步任務 export const SyncLanePriority: LanePriority = 15; export const SyncBatchedLanePriority: LanePriority = 14; // 用戶事件 const InputDiscreteHydrationLanePriority: LanePriority = 13; export const InputDiscreteLanePriority: LanePriority = 12; const InputContinuousHydrationLanePriority: LanePriority = 11; export const InputContinuousLanePriority: LanePriority = 10; const DefaultHydrationLanePriority: LanePriority = 9; export const DefaultLanePriority: LanePriority = 8; const TransitionHydrationPriority: LanePriority = 7; export const TransitionPriority: LanePriority = 6; const RetryLanePriority: LanePriority = 5; const SelectiveHydrationLanePriority: LanePriority = 4; const IdleHydrationLanePriority: LanePriority = 3; const IdleLanePriority: LanePriority = 2; const OffscreenLanePriority: LanePriority = 1; export const NoLanePriority: LanePriority = 0;
createUpdate
export function createUpdate(eventTime: number, lane: Lane): Update<*> { const update: Update<*> = { eventTime, // 更新時間 lane, // 優(yōu)先級 tag: UpdateState,//更新 payload: null,// 需要更新的內(nèi)容 callback: null, // 更新完后的回調(diào) next: null, // 指向下一個更新 }; return update; }
createUpdate
函數(shù)入?yún)?code>eventTime和lane
,輸出一個update
對象,而對象中的tag
表示此對象要進行什么樣的操作。
export const UpdateState = 0;// 更新 export const ReplaceState = 1;//替換 export const ForceUpdate = 2;//強制更新 export const CaptureUpdate = 3;//xx更新
createUpdate
就是單純的給每一個任務進行包裝,作為一個個體推入到更新隊列中。
enqueueUpdate
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { // 獲取當前更新隊列?為啥呢?因為無法保證react是不是還有正在更新或者沒有更新完畢的任務 const updateQueue = fiber.updateQueue; // 如果更新隊列為空,則表示fiber還未渲染,直接退出 if (updateQueue === null) { // Only occurs if the fiber has been unmounted. return; } const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; const pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. // 還記得那個更新對象嗎?update.next => // 如果pedding位null,表示第一次渲染,那么他的指針為update本身 update.next = update; } else { // 將update插入到更新隊列循環(huán)當中 update.next = pending.next; pending.next = update; } sharedQueue.pending = update; if (__DEV__) { if ( currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate ) { console.error( 'An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.', ); didWarnUpdateInsideUpdate = true; } } }
這一步就是把需要更新的對象,與fiber
更新隊列關聯(lián)起來。
總結(jié)
React
通過獲取事件的優(yōu)先級,處理具有同樣優(yōu)先級的事件,創(chuàng)建更新對象并與fiber
的更新隊列關聯(lián)起來。到這一步updateContainer
這個流程就走完了,也下面就是開始他的協(xié)調(diào)階段了。
協(xié)調(diào)與調(diào)度
協(xié)調(diào)與調(diào)度的流程大致如圖所示:
reconciler流程
React
的reconciler
流程以scheduleUpdateOnFiber
為入口,并在checkForNestedUpdates
里面處理任務更新的嵌套層數(shù),如果嵌套層數(shù)過大( >50
),就會認為是無效更新,則會拋出異常。之后便根據(jù)markUpdateLaneFromFiberToRoot
對當前的fiber
樹,自底向上的遞歸fiber
的lane
,根據(jù)lane
做二進制比較或者位運算處理。詳情如下:
- 如果當前執(zhí)行任務的優(yōu)先級為同步,則去判斷有無正在執(zhí)行的
React
任務。如果沒有則執(zhí)行ensureRootIsScheduled
,進行調(diào)度處理。 - 如果當前任務優(yōu)先級是異步執(zhí)行,則執(zhí)行
ensureRootIsScheduled
進行調(diào)度處理。
export function scheduleUpdateOnFiber( fiber: Fiber, lane: Lane, eventTime: number, ) { // 檢查嵌套層數(shù),避免是循環(huán)做無效操作 checkForNestedUpdates(); warnAboutRenderPhaseUpdatesInDEV(fiber); // 更新當前更新隊列里面的任務優(yōu)先級,自底而上更新child.fiberLanes const root = markUpdateLaneFromFiberToRoot(fiber, lane); if (root === null) { warnAboutUpdateOnUnmountedFiberInDEV(fiber); return null; } // Mark that the root has a pending update. // 標記root有更新的,執(zhí)行它 markRootUpdated(root, lane, eventTime); if (root === workInProgressRoot) { // Received an update to a tree that's in the middle of rendering. Mark // that there was an interleaved update work on this root. Unless the // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render // phase update. In that case, we don't treat render phase updates as if // they were interleaved, for backwards compat reasons. if ( deferRenderPhaseUpdateToNextBatch || (executionContext & RenderContext) === NoContext ) { workInProgressRootUpdatedLanes = mergeLanes( workInProgressRootUpdatedLanes, lane, ); } if (workInProgressRootExitStatus === RootSuspendedWithDelay) { // The root already suspended with a delay, which means this render // definitely won't finish. Since we have a new update, let's mark it as // suspended now, right before marking the incoming update. This has the // effect of interrupting the current render and switching to the update. // TODO: Make sure this doesn't override pings that happen while we've // already started rendering. markRootSuspended(root, workInProgressRootRenderLanes); } } // TODO: requestUpdateLanePriority also reads the priority. Pass the // priority as an argument to that function and this one. // 獲取當前優(yōu)先級層次 const priorityLevel = getCurrentPriorityLevel(); // 同步任務,采用同步更新的方式 if (lane === SyncLane) { if ( // Check if we're inside unbatchedUpdates (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering (executionContext & (RenderContext | CommitContext)) === NoContext ) { // Register pending interactions on the root to avoid losing traced interaction data. // 同步而且沒有react任務在執(zhí)行,調(diào)用performSyncWorkOnRoot schedulePendingInteractions(root, lane); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed // root inside of batchedUpdates should be synchronous, but layout updates // should be deferred until the end of the batch. performSyncWorkOnRoot(root); } else { // 如果有正在執(zhí)行的react任務,那么執(zhí)行它ensureRootIsScheduled去復用當前正在執(zhí)行的任務 // 跟本次更新一起進行 ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, lane); if (executionContext === NoContext) { // Flush the synchronous work now, unless we're already working or inside // a batch. This is intentionally inside scheduleUpdateOnFiber instead of // scheduleCallbackForFiber to preserve the ability to schedule a callback // without immediately flushing it. We only do this for user-initiated // updates, to preserve historical behavior of legacy mode. resetRenderTimer(); flushSyncCallbackQueue(); } } } else { // Schedule a discrete update but only if it's not Sync. // 如果此次是異步任務 if ( (executionContext & DiscreteEventContext) !== NoContext && // Only updates at user-blocking priority or greater are considered // discrete, even inside a discrete event. (priorityLevel === UserBlockingSchedulerPriority || priorityLevel === ImmediateSchedulerPriority) ) { // This is the result of a discrete event. Track the lowest priority // discrete update per root so we can flush them early, if needed. if (rootsWithPendingDiscreteUpdates === null) { rootsWithPendingDiscreteUpdates = new Set([root]); } else { rootsWithPendingDiscreteUpdates.add(root); } } // Schedule other updates after in case the callback is sync. // 可以中斷更新,只要調(diào)用ensureRootIsScheduled => performConcurrentWorkOnRoot ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, lane); } // We use this when assigning a lane for a transition inside // `requestUpdateLane`. We assume it's the same as the root being updated, // since in the common case of a single root app it probably is. If it's not // the same root, then it's not a huge deal, we just might batch more stuff // together more than necessary. mostRecentlyUpdatedRoot = root; }
同步任務類型執(zhí)行機制
當任務的類型為同步任務,并且當前的js
主線程空閑,會通過 performSyncWorkOnRoot(root)
方法開始執(zhí)行同步任務。
performSyncWorkOnRoot
里面主要做了兩件事:
renderRootSync
從根節(jié)點開始進行同步渲染任務commitRoot
執(zhí)行commit
流程
當前js
線程中有正在執(zhí)行的任務時候,就會觸發(fā)ensureRootIsScheduled
函數(shù)。 ensureRootIsScheduled
里面主要是處理當前加入的更新任務的lane
是否有變化:
- 如果沒有變化則表示跟當前的
schedule
一起執(zhí)行。 - 如果有則創(chuàng)建新的
schedule
。 - 調(diào)用
performSyncWorkOnRoot
執(zhí)行同步任務。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { const existingCallbackNode = root.callbackNode; // Check if any lanes are being starved by other work. If so, mark them as // expired so we know to work on those next. markStarvedLanesAsExpired(root, currentTime); // Determine the next lanes to work on, and their priority. const nextLanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, ); // This returns the priority level computed during the `getNextLanes` call. const newCallbackPriority = returnNextLanesPriority(); if (nextLanes === NoLanes) { // Special case: There's nothing to work on. if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); root.callbackNode = null; root.callbackPriority = NoLanePriority; } return; } // Check if there's an existing task. We may be able to reuse it. if (existingCallbackNode !== null) { const existingCallbackPriority = root.callbackPriority; if (existingCallbackPriority === newCallbackPriority) { // The priority hasn't changed. We can reuse the existing task. Exit. return; } // The priority changed. Cancel the existing callback. We'll schedule a new // one below. cancelCallback(existingCallbackNode); } // Schedule a new callback. let newCallbackNode; if (newCallbackPriority === SyncLanePriority) { // Special case: Sync React callbacks are scheduled on a special // internal queue // 同步任務調(diào)用performSyncWorkOnRoot newCallbackNode = scheduleSyncCallback( performSyncWorkOnRoot.bind(null, root), ); } else if (newCallbackPriority === SyncBatchedLanePriority) { newCallbackNode = scheduleCallback( ImmediateSchedulerPriority, performSyncWorkOnRoot.bind(null, root), ); } else { // 異步任務調(diào)用 performConcurrentWorkOnRoot const schedulerPriorityLevel = lanePriorityToSchedulerPriority( newCallbackPriority, ); newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), ); } root.callbackPriority = newCallbackPriority; root.callbackNode = newCallbackNode; }
所以任務類型為同步的時候,不管js
線程空閑與否,都會走到performSyncWorkOnRoot
,進而走renderRootSync
、workLoopSync
流程,而在workLoopSync
中,只要workInProgress fiber
不為null
,則會一直循環(huán)執(zhí)行performUnitOfWork
,而performUnitOfWork
中會去執(zhí)行beginWork
和completeWork
,也就是上一章里面說的beginWork
流程去創(chuàng)建每一個fiber
節(jié)點
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); } }
異步任務類型執(zhí)行機制
異步任務則會去執(zhí)行performConcurrentWorkOnRoot
,進而去執(zhí)行renderRootConcurrent
、workLoopConcurrent
,但是與同步任務不同的是異步任務是可以中斷的,這個可中斷的關鍵字就在于shouldYield
,它本身返回值是一個false
,為true
則可以中斷。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function workLoopConcurrent() { while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); } }
每一次在執(zhí)行performUnitOfWork
之前都會關注一下shouldYield()
返回值,也就是說的reconciler
過程可中斷的意思。
shouldYield
// packages\scheduler\src\SchedulerPostTask.js export function unstable_shouldYield() { return getCurrentTime() >= deadline; }
getCurrentTime
為new Date()
,deadline
為瀏覽器處理每一幀結(jié)束時間戳,所以這里表示的是,在瀏覽器每一幀空閑的時候,才會去處理此任務,如果當前任務在瀏覽器執(zhí)行的某一幀里面,則會中斷當前任務,等待瀏覽器當前幀執(zhí)行完畢,等到下一幀空閑的時候,才會去執(zhí)行當前任務。
所以不管在workLoopConcurrent
還是workLoopSync
中,都會根據(jù)當前的workInProgress fiber
是否為null
來進行循環(huán)調(diào)用performUnitOfWork
。根據(jù)流程圖以及上面說的這一些,可以看得出來從beginWork
到completeUnitOfWork
這個過程究竟干了什么。
這三章將會講解fiber
樹的reconcileChildren
過程、completeWork
過程、commitMutationEffects
…insertOrAppendPlacementNodeIntoContainer(DOM)
過程。這里將詳細解讀v17
版本的React
的diff算法、虛擬dom到真實dom的創(chuàng)建,函數(shù)生命鉤子的執(zhí)行流程等。
performUnitOfWork
function performUnitOfWork(unitOfWork: Fiber): void { // The current, flushed, state of this fiber is the alternate. Ideally // nothing should rely on this, but relying on it here means that we don't // need an additional field on the work in progress. const current = unitOfWork.alternate; setCurrentDebugFiberInDEV(unitOfWork); let next; if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, subtreeRenderLanes); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); } else { // beginWork next = beginWork(current, unitOfWork, subtreeRenderLanes); } resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. // completeUnitOfWork completeUnitOfWork(unitOfWork); } else { workInProgress = next; } ReactCurrentOwner.current = null; }
所以在performUnitOfWork
里面,每一次執(zhí)行beginWork
,進行workIngProgress更新,當遍歷完畢整棵fiber樹之后便會執(zhí)行completeUnitOfWork
。
beginWork
我們可以看到beginWork
就是originBeginWork
得實際執(zhí)行。我們翻開beginWork
的源碼可以看到,它便是根據(jù)不同的workInProgress.tag
執(zhí)行不同組件類型的處理函數(shù),這里就不去拆分的太細,只有有想法便會單獨出一篇文章講述這個的細節(jié),但是最后都會去調(diào)用reconcileChildren
。
completeUnitOfWork
當遍歷完畢執(zhí)行beginWork
,遍歷完畢之后就會走completeUnitOfWork
。
function completeUnitOfWork(unitOfWork: Fiber): void { // Attempt to complete the current unit of work, then move to the next // sibling. If there are no more siblings, return to the parent fiber. let completedWork = unitOfWork; do { // The current, flushed, state of this fiber is the alternate. Ideally // nothing should rely on this, but relying on it here means that we don't // need an additional field on the work in progress. const current = completedWork.alternate; const returnFiber = completedWork.return; // Check if the work completed or if something threw. if ((completedWork.flags & Incomplete) === NoFlags) { setCurrentDebugFiberInDEV(completedWork); let next; if ( !enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode ) { // 綁定事件,更新props,更新dom next = completeWork(current, completedWork, subtreeRenderLanes); } else { startProfilerTimer(completedWork); next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); } resetCurrentDebugFiberInDEV(); if (next !== null) { // Completing this fiber spawned new work. Work on that next. workInProgress = next; return; } resetChildLanes(completedWork); if ( returnFiber !== null && // Do not append effects to parents if a sibling failed to complete (returnFiber.flags & Incomplete) === NoFlags ) { // Append all the effects of the subtree and this fiber onto the effect // list of the parent. The completion order of the children affects the // side-effect order. // 把已收集到的副作用,合并到父級effect lists中 if (returnFiber.firstEffect === null) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect !== null) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } // If this fiber had side-effects, we append it AFTER the children's // side-effects. We can perform certain side-effects earlier if needed, // by doing multiple passes over the effect list. We don't want to // schedule our own side-effect on our own list because if end up // reusing children we'll schedule this effect onto itself since we're // at the end. const flags = completedWork.flags; // Skip both NoWork and PerformedWork tags when creating the effect // list. PerformedWork effect is read by React DevTools but shouldn't be // committed. // 跳過NoWork,PerformedWork在commit階段用不到 if (flags > PerformedWork) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork; } else { returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; } } } else { // This fiber did not complete because something threw. Pop values off // the stack without entering the complete phase. If this is a boundary, // capture values if possible. const next = unwindWork(completedWork, subtreeRenderLanes); // Because this fiber did not complete, don't reset its expiration time. if (next !== null) { // If completing this work spawned new work, do that next. We'll come // back here again. // Since we're restarting, remove anything that is not a host effect // from the effect tag. next.flags &= HostEffectMask; workInProgress = next; return; } if ( enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode ) { // Record the render duration for the fiber that errored. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing. let actualDuration = completedWork.actualDuration; let child = completedWork.child; while (child !== null) { actualDuration += child.actualDuration; child = child.sibling; } completedWork.actualDuration = actualDuration; } if (returnFiber !== null) { // Mark the parent fiber as incomplete and clear its effect list. returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.flags |= Incomplete; } } // 兄弟層指針 const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. workInProgress = siblingFiber; return; } // Otherwise, return to the parent completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork !== null); // We've reached the root. if (workInProgressRootExitStatus === RootIncomplete) { workInProgressRootExitStatus = RootCompleted; } }
他的作用便是逐層收集fiber
樹上已經(jīng)被打上的副作用標簽flags
,一直收集到root
上面以便于在commit
階段進行dom
的增刪改。
scheduler流程
在這里應該有很多人不明白,協(xié)調(diào)
和調(diào)度
是什么意思,通俗來講:
- 協(xié)調(diào)就是協(xié)同合作
- 調(diào)度就是執(zhí)行命令
所以在React
中協(xié)調(diào)就是一個js
線程中,需要安排很多模塊去完成整個流程,例如:同步異步lane
的處理,reconcileChildren
處理fiber
節(jié)點等,保證整個流程有條不紊的執(zhí)行。調(diào)度表現(xiàn)為讓空閑的js線程(幀層面)去執(zhí)行其他任務,這個過程稱之為調(diào)度,那么它到底是怎么去做的呢?
我們回到處理異步任務那里,我們會發(fā)現(xiàn)performConcurrentWorkOnRoot
這個函數(shù)外面包裹了一層scheduleCallback
:
newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), )
export function scheduleCallback( reactPriorityLevel: ReactPriorityLevel, callback: SchedulerCallback, options: SchedulerCallbackOptions | void | null, ) { const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel); return Scheduler_scheduleCallback(priorityLevel, callback, options); }
我們幾經(jīng)周折找到了聲明函數(shù)的地方
// packages/scheduler/src/Scheduler.js function unstable_scheduleCallback(priorityLevel, callback, options) { var currentTime = getCurrentTime(); var startTime; if (typeof options === 'object' && options !== null) { var delay = options.delay; if (typeof delay === 'number' && delay > 0) { startTime = currentTime + delay; } else { startTime = currentTime; } } else { startTime = currentTime; } var timeout; switch (priorityLevel) { case ImmediatePriority: timeout = IMMEDIATE_PRIORITY_TIMEOUT; break; case UserBlockingPriority: timeout = USER_BLOCKING_PRIORITY_TIMEOUT; break; case IdlePriority: timeout = IDLE_PRIORITY_TIMEOUT; break; case LowPriority: timeout = LOW_PRIORITY_TIMEOUT; break; case NormalPriority: default: timeout = NORMAL_PRIORITY_TIMEOUT; break; } var expirationTime = startTime + timeout; var newTask = { id: taskIdCounter++, callback, priorityLevel, startTime, expirationTime, sortIndex: -1, }; if (enableProfiling) { newTask.isQueued = false; } if (startTime > currentTime) { // This is a delayed task. newTask.sortIndex = startTime; push(timerQueue, newTask); if (peek(taskQueue) === null && newTask === peek(timerQueue)) { // All tasks are delayed, and this is the task with the earliest delay. if (isHostTimeoutScheduled) { // Cancel an existing timeout. cancelHostTimeout(); } else { isHostTimeoutScheduled = true; } // Schedule a timeout. requestHostTimeout(handleTimeout, startTime - currentTime); } } else { newTask.sortIndex = expirationTime; push(taskQueue, newTask); if (enableProfiling) { markTaskStart(newTask, currentTime); newTask.isQueued = true; } // 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); } } return newTask; }
- 當
starttime > currentTime
的時候,表示任務超時,插入超時隊列。 - 任務沒有超時,插入調(diào)度隊列
- 執(zhí)行
requestHostCallback
調(diào)度任務。
// 創(chuàng)建消息通道 const channel = new MessageChannel(); const port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; // 告知scheduler開始調(diào)度 requestHostCallback = function(callback) { scheduledHostCallback = callback; if (!isMessageLoopRunning) { isMessageLoopRunning = true; port.postMessage(null); } };
react
通過 new MessageChannel()
創(chuàng)建了消息通道,當發(fā)現(xiàn)js
線程空閑時,通過postMessage
通知 scheduler
開始調(diào)度。performWorkUntilDeadline
函數(shù)功能為處理react
調(diào)度開始時間更新到結(jié)束時間。
這里我們要關注一下設備幀速率。
forceFrameRate = function(fps) { if (fps < 0 || fps > 125) { // Using console['error'] to evade Babel and ESLint console['error']( 'forceFrameRate takes a positive int between 0 and 125, ' + 'forcing frame rates higher than 125 fps is not supported', ); return; } if (fps > 0) { yieldInterval = Math.floor(1000 / fps); } else { // reset the framerate yieldInterval = 5; } };
performWorkUntilDeadline
const performWorkUntilDeadline = () => { if (scheduledHostCallback !== null) { const currentTime = getCurrentTime(); // Yield after `yieldInterval` ms, regardless of where we are in the vsync // cycle. This means there's always time remaining at the beginning of // the message event. // 更新當前幀結(jié)束時間 deadline = currentTime + yieldInterval; const hasTimeRemaining = true; try { const hasMoreWork = scheduledHostCallback( hasTimeRemaining, currentTime, ); // 還有任務就繼續(xù)執(zhí)行 if (!hasMoreWork) { isMessageLoopRunning = false; scheduledHostCallback = null; } else { // If there's more work, schedule the next message event at the end // of the preceding one. // 沒有就postMessage port.postMessage(null); } } catch (error) { // If a scheduler task throws, exit the current browser task so the // error can be observed. port.postMessage(null); throw error; } } else { isMessageLoopRunning = false; } // Yielding to the browser will give it a chance to paint, so we can // reset this. needsPaint = false; };
總結(jié)
本文講了React
在狀態(tài)改變的時候,會根據(jù)當前任務優(yōu)先級,等一些列操作去創(chuàng)建workInProgress fiber
鏈表樹,在協(xié)調(diào)階段,會根據(jù)瀏覽器每一幀去做比較,假如瀏覽器每一幀執(zhí)行時間戳高于當前時間,則表示當前幀沒有空閑時間,當前任務則必須要等到下一個空閑幀才能去執(zhí)行的可中斷的策略。還有關于beginWork
的遍歷執(zhí)行更新fiber
的節(jié)點。那么到這里這一章就講述完畢了,下一章講一講React的diff算法
到此這篇關于react源碼層分析協(xié)調(diào)與調(diào)度的文章就介紹到這了,更多相關react協(xié)調(diào)與調(diào)度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
React報錯map()?is?not?a?function詳析
這篇文章主要介紹了React報錯map()?is?not?a?function詳析,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-08-08