一圖看懂 React 源碼中的同步更新邏輯
在 React 源碼中,scheduleUpdateOnFiber 是所有任務(wù)的唯一入口方法。我們前面分析 useState 的實(shí)現(xiàn)原理章節(jié)中,我們可以清晰的知道,當(dāng)我們調(diào)用 dispatchSetState 時,最終會調(diào)用該入口方法。
scheduleUpdateOnFiber 主要用于觸發(fā)一個 Fiber 節(jié)點(diǎn)上的調(diào)度更新任務(wù),該函數(shù)里主要有兩個核心邏輯。
// Mark that the root has a pending update.
// 標(biāo)記 root 上有一個更新任務(wù)
markRootUpdated(root, lane, eventTime);
ensureRootIsScheduled(root, eventTime);
markRootUpdated 的邏輯如下,簡單了解一下即可。
export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
) {
// 設(shè)置本次更新的優(yōu)先級
root.pendingLanes |= updateLane;
// 重置 root 應(yīng)用根節(jié)點(diǎn)的優(yōu)先級
if (updateLane !== IdleLane) {
// 由 Suspence 而掛起的 update 對應(yīng)的 lane 集合
root.suspendedLanes = NoLanes;
// 由請求成功,Suspence 取消掛起的 update 對應(yīng)的 Lane 集合
root.pingedLanes = NoLanes;
}
const eventTimes = root.eventTimes;
const index = laneToIndex(updateLane);
eventTimes[index] = eventTime;
}
ensureRootIsScheduled 的主要目的要確保 root 根節(jié)點(diǎn)被調(diào)度。在該邏輯中,會根據(jù) root.pendingLanes 信息計算出本次更新的 Lanes: nextLanes。
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
然后根據(jù) nextLanes 計算出本批次集合中優(yōu)先級最高的 Lane,作為本地任務(wù)的優(yōu)先級。
// We use the highest priority lane to represent the priority of the callback.
const newCallbackPriority = getHighestPriorityLane(nextLanes);
后續(xù)的邏輯就是取出當(dāng)前已存在的調(diào)度優(yōu)先級,與 newCallbackPriority 進(jìn)行對比,根據(jù)對比結(jié)果來執(zhí)行不同的更新方法。當(dāng)該值等于 SyncLane 時,表示為同步更新。
同步優(yōu)先級例如點(diǎn)擊事件。
然后會判斷是否支持微任務(wù)更新,如果不支持最后會執(zhí)行 scheduleCallback。
if (newCallbackPriority === SyncLane) {
if (supportsMicrotasks) {
// Flush the queue in a microtask.
if (__DEV__ && ReactCurrentActQueue.current !== null) {
// Inside `act`, use our internal `act` queue so that these get flushed
// at the end of the current scope even when using the sync version
// of `act`.
ReactCurrentActQueue.current.push(flushSyncCallbacks);
} else {
scheduleMicrotask(() => {
// In Safari, appending an iframe forces microtasks to run.
// https://github.com/facebook/react/issues/22459
// We don't support running callbacks in the middle of render
// or commit so we need to check against that.
if (
(executionContext & (RenderContext | CommitContext)) ===
NoContext
) {
// Note that this would still prematurely flush the callbacks
// if this happens outside render or commit phase (e.g. in an event).
flushSyncCallbacks();
}
});
}
} else {
// Flush the queue in an Immediate task.
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
}
scheduleSyncCallback 的邏輯,也就是同步任務(wù)的調(diào)度非常簡單,就是將執(zhí)行同步任務(wù)的回調(diào)添加到一個同步隊列 syncQueue 中。
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`.
if (syncQueue === null) {
syncQueue = [callback];
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
}
這里的 callback 是之前傳入的 performSyncWorkOnRoot,這是用來執(zhí)行同步更新任務(wù)的方法。他的邏輯主要包括:
- 調(diào)用 renderRootSync,該方法會執(zhí)行 workLoopSync,最后生成 Fiber true。
- 將創(chuàng)建完成的 Fiber tree 掛載到 root 節(jié)點(diǎn)上。
- 最后調(diào)用 commitRoot,進(jìn)入 commit 階段修改真實(shí) DOM。
function performSyncWorkOnRoot(root) {
...
let exitStatus = renderRootSync(root, lanes);
...
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
ensureRootIsScheduled(root, now());
return null;
}
workLoopSync 的邏輯也非常簡單,如下:
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
在 performUnitOfWork 中,會調(diào)用 beginWork 方法開始創(chuàng)建 Fiber 節(jié)點(diǎn)。
var next = beginWork(
current,
unitOfWork,
subtreeRenderLanes
);
總結(jié)
同步更新的過程比較簡單,從 scheduleUpdateOnFiber 到 beginWork 這中間的流程里,大多數(shù)邏輯都在進(jìn)行各種不同情況的判斷,因此源碼看上去比較吃力,實(shí)際邏輯并不是很重要,簡單了解即可,重要的是 beginWork 創(chuàng)建 Fiber 節(jié)點(diǎn)的方法,這跟我們之前文章里提到過的優(yōu)化策略是一致的。