海從不平靜。他撞擊海岸,無休無止像顆年輕的心,在搜尋。——卡爾·桑德堡javascript
每一個人,最終都會成爲歷史演化歷程中的炮灰。現在互聯網上各處充斥焦慮浮躁,萬事不能盡美,實爲惡性循環。java
React
設計體系如人類社會通常,撥動時間輪盤的那一刻,你便成了穿梭在輪片中的一粒細沙,角逐過程到處都須要亮出你的屬性,你重要嗎?你無可替代嗎?你有特殊權限嗎?沒有,那很差意思,請繼續在輪片中循環。屬於你的生命之火殆盡,前來悼念之人不少,這幕,像極了出生時的場景。react
幹啥玩意兒,這是技術文章不是抒情散文!下面進入正題。git
建立的準備上一節已經說明了,主要定義與更新相關的數據結構和變量,計算過時時間等。完成這些準備工做以後,正式進入調度工做,調度過程實現思路是:當與更新或掛載相關api被調用時,就會執行更新的邏輯,更新大體分爲如下幾個小階段github
該步驟的主要工做有如下幾點api
scheduleWorkOnParentPath
方法找到當前 Fiber
的root節點expirationTime
,若是大於當前節點,則將其值賦值爲當前節點的 expirationTime
值。同時,childExpirationTime
的值也是該的邏輯export function scheduleUpdateOnFiber( fiber: Fiber, expirationTime: ExpirationTime, ) { checkForNestedUpdates(); warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber); const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime); if (root === null) { warnAboutUpdateOnUnmountedFiberInDEV(fiber); return; } checkForInterruption(fiber, expirationTime); recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the // priority as an argument to that function and this one. const priorityLevel = getCurrentPriorityLevel(); if (expirationTime === Sync) { 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. schedulePendingInteractions(root, expirationTime); performSyncWorkOnRoot(root); } else { ensureRootIsScheduled(root); schedulePendingInteractions(root, expirationTime); if (executionContext === NoContext) { flushSyncCallbackQueue(); } } } else { ensureRootIsScheduled(root); schedulePendingInteractions(root, expirationTime); } ... } export const scheduleWork = scheduleUpdateOnFiber; 複製代碼
若是過時時間等於咱們定義的Sync常量對應值,則進一步判斷此次更新的狀態,若是不是 batchUpdates
何時不是這個狀態呢?咱們前面認識過,好比reder時,判斷完這個狀態後還須要保證此次的更新渲染已準備好,則開始處理。不過處理以前,還要進行一個操做就是pending interaction,與咱們動做相關的內容數據須要保存於 pendingInteractionMap
中。bash
function scheduleInteractions(root, expirationTime, interactions) { if (!enableSchedulerTracing) { return; } if (interactions.size > 0) { const pendingInteractionMap = root.pendingInteractionMap; const pendingInteractions = pendingInteractionMap.get(expirationTime); if (pendingInteractions != null) { interactions.forEach(interaction => { if (!pendingInteractions.has(interaction)) { // Update the pending async work count for previously unscheduled interaction. interaction.__count++; } pendingInteractions.add(interaction); }); } else { pendingInteractionMap.set(expirationTime, new Set(interactions)); // Update the pending async work count for the current interactions. interactions.forEach(interaction => { interaction.__count++; }); } const subscriber = __subscriberRef.current; if (subscriber !== null) { const threadID = computeThreadID(root, expirationTime); subscriber.onWorkScheduled(interactions, threadID); } } } 複製代碼
通過以上處理,就能進入 performSyncWorkOnRoot
處理了微信
function performSyncWorkOnRoot(root) { // Check if there's expired work on this root. Otherwise, render at Sync. const lastExpiredTime = root.lastExpiredTime; const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync; if (root.finishedExpirationTime === expirationTime) { commitRoot(root); } ... } 複製代碼
好了,到這裏一個expirationTime
爲 Sync
的且不是unbatchedUpdates,的調度就完成了,咱們發現這條流水線的操做仍是容易理解的,好,咱們如今進入另外一個分支,就是 batchedUpdates
markdown
ensureRootIsScheduled(root); schedulePendingInteractions(root, expirationTime); 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. flushSyncCallbackQueue(); } 複製代碼
首先須要確保一點,Root是否已經處理過調度相關工做,經過 ensureRootIsScheduled
方法爲root建立調度任務,且一個root只有一個task,假如某個root已經存在了任務,換言之已經調度過,那麼咱們須要從新爲這個task計算一些值。然後一樣有一個 schedulePendingInteractions
,用來處理交互引發的更新,方式與上面提到的 pending interaction
相似。數據結構
另外,若是executionContext
爲NoContext
,則須要刷新用於處理同步更新的回調隊列 flushSyncCallbackQueue
,該方法定義在 SchedulerWithReactIntegration.js
中。
如此,周而復始,完成更新的調度過程,最終調用 performSyncWorkOnRoot
,進入下一階段
一樣的選擇題,當前是否能直接去提交更新,yes or no ?
if (root.finishedExpirationTime === expirationTime) { // There's already a pending commit at this expiration time. // TODO: This is poorly factored. This case only exists for the // batch.commit() API. commitRoot(root); } 複製代碼
這種狀況是不多的,通常會進入這個判斷的else,也就是
... workLoopSync(); ... function workLoopSync() { // Already timed out, so perform work without checking if we need to yield. while (workInProgress !== null) { workInProgress = performUnitOfWork(workInProgress); } } 複製代碼
又開始了遍歷,這個遍歷中一樣有咱們上節分析過一些技巧,好比unitOfWork.alternate
用於節點屬性的對比與暫存
function performUnitOfWork(unitOfWork: Fiber): Fiber | null { // 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; startWorkTimer(unitOfWork); setCurrentDebugFiberInDEV(unitOfWork); let next; if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, renderExpirationTime); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); } else { next = beginWork(current, unitOfWork, renderExpirationTime); } resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. next = completeUnitOfWork(unitOfWork); } ReactCurrentOwner.current = null; return next; } 複製代碼
能夠看到執行完相關操做後,隨着 beginWork
函數的調用正式進入更新階段。
該部分主要的工做就是更新,更新什麼呢?咱們第一節講到 React
不一樣的組件使用?typeof 指定,針對這些不一樣類型的組件,定義了各自的處理方法,咱們以經常使用的 ClassComponent
爲例。
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { const updateExpirationTime = workInProgress.expirationTime; ... 複製代碼
然後首先判斷當前的更新節點是否爲空,若不爲空,則執行相關邏輯
... if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps !== newProps || hasLegacyContextChanged() || // Force a re-render if the implementation changed due to hot reload: (__DEV__ ? workInProgress.type !== current.type : false) ) { // If props or context changed, mark the fiber as having performed work. // This may be unset if the props are determined to be equal later (memo). didReceiveUpdate = true; } else if (updateExpirationTime < renderExpirationTime) { didReceiveUpdate = false; ... 複製代碼
此刻略知一二,先後props是否發生更改?根據不一樣的條件判斷爲 didReceiveUpdate
賦值。然後根據當前 workInProgress
的tag值判斷當前的節點對應組件類型是什麼,根據不一樣類型,進入不一樣方法進行處理。
switch (workInProgress.tag) { ... } 複製代碼
然後,一樣根據該tag,執行更新組件邏輯
case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); } 複製代碼
更新組件過程當中,若是還有子節點,須要調度並更新
export function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderExpirationTime: ExpirationTime, ) { if (current === null) { // If this is a fresh new component that hasn't been rendered yet, we // won't update its child set by applying minimal side-effects. Instead, // we will add them all to the child before it gets rendered. That means // we can optimize this reconciliation pass by not tracking side-effects. workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderExpirationTime, ); } else { // If the current child is the same as the work in progress, it means that // we haven't yet started any work on these children. Therefore, we use // the clone algorithm to create a copy of all the current children. // If we had any progressed work already, that is invalid at this point so // let's throw it out. workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderExpirationTime, ); } } 複製代碼
其子節點的 Fiber
調度定義在 ReactChildFiber.js
中,這裏不展開了。
輪迴中完成以上調度過程,也該到了提交更新的時候了,該方法咱們在剛開始就講到了,那時略過,如今拾起。
function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority( ImmediatePriority, commitRootImpl.bind(null, root, renderPriorityLevel), ); return null; } 複製代碼
具體的實如今 commitRootImpl
方法中,該方法調用 prepareForCommit
爲更新作準備,最終根據更新的類型不一樣使用不一樣策略進行更新
let primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { case Placement: { commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is // inserted, before any life-cycles like componentDidMount gets called. // TODO: findDOMNode doesn't rely on this any more but isMounted does // and isMounted is deprecated anyway so we should be able to kill this. nextEffect.effectTag &= ~Placement; break; } case PlacementAndUpdate: { // Placement commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is // inserted, before any life-cycles like componentDidMount gets called. nextEffect.effectTag &= ~Placement; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Hydrating: { nextEffect.effectTag &= ~Hydrating; break; } case HydratingAndUpdate: { nextEffect.effectTag &= ~Hydrating; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } 複製代碼
提交更新相關的處理定義於 ReactFiberCommitWork.js
一樣也要藉助 tag,作不一樣策略的處理。
至此完成了任務調度的全部工做,固然在後面的過程,事件相關的處理是隻字未提,React最新源碼對於事件系統作了很大改動,咱們放在後面章節詳細講解。React
源碼設計之精妙沒法言盡,而且只是略讀,完成本系列的粗略講解後,後續會有更深刻源碼講解。讀源碼爲了什麼?
好了,廢話很少說,若是看了不懂請移步公衆號或者B站查看視頻講解。若是有意見或疑問,請私信聯繫。
我是合一,英雄再會。