react源碼層分析協(xié)調(diào)與調(diào)度
requestEventTime
其實(shí)在React執(zhí)行過(guò)程中,會(huì)有數(shù)不清的任務(wù)要去執(zhí)行,但是他們會(huì)有一個(gè)優(yōu)先級(jí)的判定,假如兩個(gè)事件的優(yōu)先級(jí)一樣,那么React是怎么去判定他們兩誰(shuí)先執(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 正在計(jì)算
// 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;
}
// 重新計(jì)算currentEventTime,當(dāng)執(zhí)行被中斷后
currentEventTime = now();
return currentEventTime;
}RenderContext與CommitContext表示正在計(jì)算更新和正在提交更新,返回now()。- 如果是瀏覽器事件正在執(zhí)行中,返回上一次的
currentEventTime。 - 如果終止或者中斷react任務(wù)執(zhí)行的時(shí)候,則重新獲取執(zhí)行時(shí)間
now()。 - 獲取的時(shí)間越小,則執(zhí)行的優(yōu)先級(jí)越高。
now()并不是單純的new Date(),而是判定兩次更新任務(wù)的時(shí)間是否小于10ms,來(lái)決定是否復(fù)用上一次的更新時(shí)間Scheduler_now的。
export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
其實(shí)各位猜想一下,對(duì)于10ms級(jí)別的任務(wù)間隙時(shí)間,幾乎是可以忽略不計(jì)的,那么這里就可以視為同樣的任務(wù),不需要有很大的性能開銷,有利于批量更新。
requestUpdateLane
requestEventTime位每一個(gè)需要執(zhí)行的任務(wù)打上了觸發(fā)更新時(shí)間標(biāo)簽,那么任務(wù)的優(yōu)先級(jí)還需要進(jìn)一步的確立,requestUpdateLane就是用來(lái)獲取每一個(gè)任務(wù)執(zhí)行的優(yōu)先級(jí)的。
// 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í)行任務(wù)的優(yōu)先級(jí),便于調(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
) {
// 通過(guò)findUpdateLane函數(shù)重新計(jì)算lane
lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
} else {
// 根據(jù)優(yōu)先級(jí)計(jì)算法則計(jì)算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ù)計(jì)算得到的 schedulerLanePriority,計(jì)算更新的優(yōu)先級(jí) lane
lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
}
return lane;
}- 通過(guò)
getCurrentPriorityLevel獲得所有執(zhí)行任務(wù)的調(diào)度優(yōu)先級(jí)schedulerPriority。 - 通過(guò)
findUpdateLane計(jì)算lane,作為更新中的優(yōu)先級(jí)。
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,
);
}相關(guān)參考視頻講解:進(jìn)入學(xué)習(xí)
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';
// 同步任務(wù)
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, // 更新時(shí)間
lane, // 優(yōu)先級(jí)
tag: UpdateState,//更新
payload: null,// 需要更新的內(nèi)容
callback: null, // 更新完后的回調(diào)
next: null, // 指向下一個(gè)更新
};
return update;
}createUpdate函數(shù)入?yún)?code>eventTime和lane,輸出一個(gè)update對(duì)象,而對(duì)象中的tag表示此對(duì)象要進(jìn)行什么樣的操作。
export const UpdateState = 0;// 更新 export const ReplaceState = 1;//替換 export const ForceUpdate = 2;//強(qiáng)制更新 export const CaptureUpdate = 3;//xx更新
createUpdate就是單純的給每一個(gè)任務(wù)進(jìn)行包裝,作為一個(gè)個(gè)體推入到更新隊(duì)列中。
enqueueUpdate
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// 獲取當(dāng)前更新隊(duì)列?為啥呢?因?yàn)闊o(wú)法保證react是不是還有正在更新或者沒有更新完畢的任務(wù)
const updateQueue = fiber.updateQueue;
// 如果更新隊(duì)列為空,則表示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.
// 還記得那個(gè)更新對(duì)象嗎?update.next =>
// 如果pedding位null,表示第一次渲染,那么他的指針為update本身
update.next = update;
} else {
// 將update插入到更新隊(duì)列循環(huán)當(dāng)中
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;
}
}
}這一步就是把需要更新的對(duì)象,與fiber更新隊(duì)列關(guān)聯(lián)起來(lái)。
總結(jié)
React通過(guò)獲取事件的優(yōu)先級(jí),處理具有同樣優(yōu)先級(jí)的事件,創(chuàng)建更新對(duì)象并與fiber的更新隊(duì)列關(guān)聯(lián)起來(lái)。到這一步updateContainer這個(gè)流程就走完了,也下面就是開始他的協(xié)調(diào)階段了。
協(xié)調(diào)與調(diào)度
協(xié)調(diào)與調(diào)度的流程大致如圖所示:

reconciler流程
React的reconciler流程以scheduleUpdateOnFiber為入口,并在checkForNestedUpdates里面處理任務(wù)更新的嵌套層數(shù),如果嵌套層數(shù)過(guò)大( >50 ),就會(huì)認(rèn)為是無(wú)效更新,則會(huì)拋出異常。之后便根據(jù)markUpdateLaneFromFiberToRoot對(duì)當(dāng)前的fiber樹,自底向上的遞歸fiber的lane,根據(jù)lane做二進(jìn)制比較或者位運(yùn)算處理。詳情如下:
- 如果當(dāng)前執(zhí)行任務(wù)的優(yōu)先級(jí)為同步,則去判斷有無(wú)正在執(zhí)行的
React任務(wù)。如果沒有則執(zhí)行ensureRootIsScheduled,進(jìn)行調(diào)度處理。 - 如果當(dāng)前任務(wù)優(yōu)先級(jí)是異步執(zhí)行,則執(zhí)行
ensureRootIsScheduled進(jìn)行調(diào)度處理。
export function scheduleUpdateOnFiber(
fiber: Fiber, lane: Lane, eventTime: number,
) {
// 檢查嵌套層數(shù),避免是循環(huán)做無(wú)效操作
checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);
// 更新當(dāng)前更新隊(duì)列里面的任務(wù)優(yōu)先級(jí),自底而上更新child.fiberLanes
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return null;
}
// Mark that the root has a pending update.
// 標(biāo)記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.
// 獲取當(dāng)前優(yōu)先級(jí)層次
const priorityLevel = getCurrentPriorityLevel();
// 同步任務(wù),采用同步更新的方式
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任務(wù)在執(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任務(wù),那么執(zhí)行它ensureRootIsScheduled去復(fù)用當(dāng)前正在執(zhí)行的任務(wù)
// 跟本次更新一起進(jìn)行
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.
// 如果此次是異步任務(wù)
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;
}同步任務(wù)類型執(zhí)行機(jī)制
當(dāng)任務(wù)的類型為同步任務(wù),并且當(dāng)前的js主線程空閑,會(huì)通過(guò) performSyncWorkOnRoot(root) 方法開始執(zhí)行同步任務(wù)。
performSyncWorkOnRoot 里面主要做了兩件事:
renderRootSync從根節(jié)點(diǎn)開始進(jìn)行同步渲染任務(wù)commitRoot執(zhí)行commit流程
當(dāng)前js線程中有正在執(zhí)行的任務(wù)時(shí)候,就會(huì)觸發(fā)ensureRootIsScheduled函數(shù)。 ensureRootIsScheduled里面主要是處理當(dāng)前加入的更新任務(wù)的lane是否有變化:
- 如果沒有變化則表示跟當(dāng)前的
schedule一起執(zhí)行。 - 如果有則創(chuàng)建新的
schedule。 - 調(diào)用
performSyncWorkOnRoot執(zhí)行同步任務(wù)。
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
// 同步任務(wù)調(diào)用performSyncWorkOnRoot
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
performSyncWorkOnRoot.bind(null, root),
);
} else {
// 異步任務(wù)調(diào)用 performConcurrentWorkOnRoot
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}所以任務(wù)類型為同步的時(shí)候,不管js線程空閑與否,都會(huì)走到performSyncWorkOnRoot,進(jìn)而走renderRootSync、workLoopSync流程,而在workLoopSync中,只要workInProgress fiber不為null,則會(huì)一直循環(huán)執(zhí)行performUnitOfWork,而performUnitOfWork中會(huì)去執(zhí)行beginWork和completeWork,也就是上一章里面說(shuō)的beginWork流程去創(chuàng)建每一個(gè)fiber節(jié)點(diǎn)
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}異步任務(wù)類型執(zhí)行機(jī)制
異步任務(wù)則會(huì)去執(zhí)行performConcurrentWorkOnRoot,進(jìn)而去執(zhí)行renderRootConcurrent、workLoopConcurrent,但是與同步任務(wù)不同的是異步任務(wù)是可以中斷的,這個(gè)可中斷的關(guān)鍵字就在于shouldYield,它本身返回值是一個(gè)false,為true則可以中斷。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}每一次在執(zhí)行performUnitOfWork之前都會(huì)關(guān)注一下shouldYield()返回值,也就是說(shuō)的reconciler過(guò)程可中斷的意思。
shouldYield
// packages\scheduler\src\SchedulerPostTask.js
export function unstable_shouldYield() {
return getCurrentTime() >= deadline;
}getCurrentTime為new Date(),deadline為瀏覽器處理每一幀結(jié)束時(shí)間戳,所以這里表示的是,在瀏覽器每一幀空閑的時(shí)候,才會(huì)去處理此任務(wù),如果當(dāng)前任務(wù)在瀏覽器執(zhí)行的某一幀里面,則會(huì)中斷當(dāng)前任務(wù),等待瀏覽器當(dāng)前幀執(zhí)行完畢,等到下一幀空閑的時(shí)候,才會(huì)去執(zhí)行當(dāng)前任務(wù)。
所以不管在workLoopConcurrent還是workLoopSync中,都會(huì)根據(jù)當(dāng)前的workInProgress fiber是否為null來(lái)進(jìn)行循環(huán)調(diào)用performUnitOfWork。根據(jù)流程圖以及上面說(shuō)的這一些,可以看得出來(lái)從beginWork到completeUnitOfWork這個(gè)過(guò)程究竟干了什么。
這三章將會(huì)講解fiber樹的reconcileChildren過(guò)程、completeWork過(guò)程、commitMutationEffects…insertOrAppendPlacementNodeIntoContainer(DOM)過(guò)程。這里將詳細(xì)解讀v17版本的React的diff算法、虛擬dom到真實(shí)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,進(jìn)行workIngProgress更新,當(dāng)遍歷完畢整棵fiber樹之后便會(huì)執(zhí)行completeUnitOfWork。
beginWork


我們可以看到beginWork就是originBeginWork得實(shí)際執(zhí)行。我們翻開beginWork的源碼可以看到,它便是根據(jù)不同的workInProgress.tag執(zhí)行不同組件類型的處理函數(shù),這里就不去拆分的太細(xì),只有有想法便會(huì)單獨(dú)出一篇文章講述這個(gè)的細(xì)節(jié),但是最后都會(huì)去調(diào)用reconcileChildren。
completeUnitOfWork
當(dāng)遍歷完畢執(zhí)行beginWork,遍歷完畢之后就會(huì)走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.
// 把已收集到的副作用,合并到父級(jí)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.
// 跳過(guò)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)被打上的副作用標(biāo)簽flags,一直收集到root上面以便于在commit階段進(jìn)行dom的增刪改。

scheduler流程
在這里應(yīng)該有很多人不明白,協(xié)調(diào)和調(diào)度是什么意思,通俗來(lái)講:
- 協(xié)調(diào)就是協(xié)同合作
- 調(diào)度就是執(zhí)行命令
所以在React中協(xié)調(diào)就是一個(gè)js線程中,需要安排很多模塊去完成整個(gè)流程,例如:同步異步lane的處理,reconcileChildren處理fiber節(jié)點(diǎn)等,保證整個(gè)流程有條不紊的執(zhí)行。調(diào)度表現(xiàn)為讓空閑的js線程(幀層面)去執(zhí)行其他任務(wù),這個(gè)過(guò)程稱之為調(diào)度,那么它到底是怎么去做的呢?
我們回到處理異步任務(wù)那里,我們會(huì)發(fā)現(xiàn)performConcurrentWorkOnRoot這個(gè)函數(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;
}- 當(dāng)
starttime > currentTime的時(shí)候,表示任務(wù)超時(shí),插入超時(shí)隊(duì)列。 - 任務(wù)沒有超時(shí),插入調(diào)度隊(duì)列
- 執(zhí)行
requestHostCallback調(diào)度任務(wù)。
// 創(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通過(guò) new MessageChannel() 創(chuàng)建了消息通道,當(dāng)發(fā)現(xiàn)js線程空閑時(shí),通過(guò)postMessage通知 scheduler開始調(diào)度。performWorkUntilDeadline函數(shù)功能為處理react調(diào)度開始時(shí)間更新到結(jié)束時(shí)間。
這里我們要關(guān)注一下設(shè)備幀速率。
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.
// 更新當(dāng)前幀結(jié)束時(shí)間
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
// 還有任務(wù)就繼續(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)改變的時(shí)候,會(huì)根據(jù)當(dāng)前任務(wù)優(yōu)先級(jí),等一些列操作去創(chuàng)建workInProgress fiber鏈表樹,在協(xié)調(diào)階段,會(huì)根據(jù)瀏覽器每一幀去做比較,假如瀏覽器每一幀執(zhí)行時(shí)間戳高于當(dāng)前時(shí)間,則表示當(dāng)前幀沒有空閑時(shí)間,當(dāng)前任務(wù)則必須要等到下一個(gè)空閑幀才能去執(zhí)行的可中斷的策略。還有關(guān)于beginWork的遍歷執(zhí)行更新fiber的節(jié)點(diǎn)。那么到這里這一章就講述完畢了,下一章講一講React的diff算法
到此這篇關(guān)于react源碼層分析協(xié)調(diào)與調(diào)度的文章就介紹到這了,更多相關(guān)react協(xié)調(diào)與調(diào)度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react 移動(dòng)端實(shí)現(xiàn)列表左滑刪除的示例代碼
這篇文章主要介紹了react 移動(dòng)端實(shí)現(xiàn)列表左滑刪除的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
react源碼中的生命周期和事件系統(tǒng)實(shí)例解析
這篇文章主要為大家介紹了react源碼中的生命周期和事件系統(tǒng)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
React?跨端動(dòng)態(tài)化核心技術(shù)實(shí)例分析
這篇文章主要為大家介紹了React?跨端動(dòng)態(tài)化核心技術(shù)實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
React?實(shí)現(xiàn)具備吸頂和吸底功能組件實(shí)例
這篇文章主要為大家介紹了React?實(shí)現(xiàn)具備吸頂和吸底功能組件實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
React報(bào)錯(cuò)map()?is?not?a?function詳析
這篇文章主要介紹了React報(bào)錯(cuò)map()?is?not?a?function詳析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08

