老的react架構在渲染時會有一些性能問題,從setstate到render,程序一直在跑,一直到render完成。才能繼續下一步操做。若是組件比較多,或者有複雜的計算邏輯,這之間的消耗的時間是比較多的。 假設更新一個組件須要1ms,若是有200個組件要更新,那就須要200ms,這200ms之間是不能響應的。若是這時候用戶在input框輸入什麼東西,表現出來的就是明顯的卡頓。 React這樣的調度策略對動畫的支持也很差。若是React更新一次狀態,佔用瀏覽器主線程的時間超過16.6ms,就會被人眼發覺先後兩幀不連續,呈現出動畫卡頓。javascript
react團隊通過兩年的工做,重寫了react中核心算法reconciliation。並在v16版本中發佈了這個新的特性。爲了區別以前和以後的reconciler,一般將以前的reconciler稱爲stack reconciler,重寫後的稱爲fiber reconciler,簡稱爲Fiber。java
最大的變化就是支持了任務幀,把各個任務都增長了優先級,同步和異步。好比用戶輸入input是優先級比較高的,它能夠打斷低優先級的任務。 好比再處理dom diff的時候耗時嚴重,fiber任務處理大概會有50ms的幀時長,超過這個時間就會先去看看有沒高優任務去作。而後回來作低優先級任務。node
scheduleWork - requestWork - 同步/異步 - performSyncWork- performWork - performWorkOnRoot - renderRoot/completeRoot - workLoop-performUnitOfWork-beginWork/completeUnitOfWork -updateClassComponent-reconcileChildrenAtExpirationTime- reconcileChildFibers-reconcileChildrenArray 源碼基於react v16.3.0 (8e3d94ff)react
Component.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
主要是把任務插入fiber的update queue,而後調度任務算法
enqueueSetState(instance, partialState, callback) { const fiber = ReactInstanceMap.get(instance); callback = callback === undefined ? null : callback; const expirationTime = computeExpirationForFiber(fiber); const update = { expirationTime, partialState, callback, isReplace: false, isForced: false, capturedValue: null, next: null, }; insertUpdateIntoFiber(fiber, update); scheduleWork(fiber, expirationTime); },
插入fiber兩棵樹的update queueapi
每一個react 結點都有2個fiber鏈表,一個叫current fiber,一個叫alternate fiber,而每一個鏈表又對應兩個updateQueue。 而currentFiber.alternate = alternateFiber; alternateFiber.alternate = currentFiber。經過alternate屬性鏈接起來。初始化的時候,alternate fiber是current fiber 的clone。 處理diff的時候,操做的是alternateFiber,處理完diff,讓currentFiber = alternateFiber;這樣一個處理就完成了。瀏覽器
scheduleWork會更新每一個節點的優先級,而後循環到root,之後的操做都從root開始遍歷。架構
{ NoWork: 0, // No work is pending. SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects. AnimationPriority: 2, // Needs to complete before the next frame. HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive. LowPriority: 4, // Data fetching, or result from updating stores. OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible. };
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) { return scheduleWorkImpl(fiber, expirationTime, false); } function scheduleWorkImpl( fiber: Fiber, expirationTime: ExpirationTime, isErrorRecovery: boolean, ) { recordScheduleUpdate(); // 記錄更新,實際啥也沒幹 let node = fiber; while (node !== null) { // Walk the parent path to the root and update each node's // expiration time. // 更新每一個node的優先級 if ( node.expirationTime === NoWork || node.expirationTime > expirationTime ) { node.expirationTime = expirationTime; } if (node.alternate !== null) { if ( node.alternate.expirationTime === NoWork || node.alternate.expirationTime > expirationTime ) { node.alternate.expirationTime = expirationTime; } } if (node.return === null) { if (node.tag === HostRoot) { const root: FiberRoot = (node.stateNode: any); if ( !isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime ) { // This is an interruption. (Used for performance tracking.) interruptedBy = fiber; resetStack(); } if ( // If we're in the render phase, we don't need to schedule this root // for an update, because we'll do it before we exit... !isWorking || isCommitting || // ...unless this is a different root than the one we're rendering. nextRoot !== root ) { // Add this root to the root schedule. requestWork(root, expirationTime); } } else { } return; } } node = node.return; } }
同步執行performSyncWork,異步執行scheduleCallbackWithExpiration, scheduleCallbackWithExpiration會調瀏覽器的requestidlecallback,在瀏覽器空閒的時候進行處理。 react還對這個api作了polyfillapp
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) { if (isRendering) { return; } if (isBatchingUpdates) { // 這裏是BatchingUpdates的處理。 // Flush work at the end of the batch. if (isUnbatchingUpdates) { // ...unless we're inside unbatchedUpdates, in which case we should // flush it now. nextFlushedRoot = root; nextFlushedExpirationTime = Sync; performWorkOnRoot(root, Sync, false); } return; } if (expirationTime === Sync) { performSyncWork(); } else { scheduleCallbackWithExpiration(expirationTime); } }
這裏會找到高優任務先執行。 同步任務會直接調用performWorkOnRoot進行下一步, 異步任務也會調performWorkOnRoot,但處理不太同樣 若是有上次遺留的任務,留到空閒時運行less
function performSyncWork() { performWork(Sync, false, null); } function performWork( minExpirationTime: ExpirationTime, isAsync: boolean, dl: Deadline | null, ) { deadline = dl; findHighestPriorityRoot(); if (isAsync) { while ( nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime) && (!deadlineDidExpire || recalculateCurrentTime() >= nextFlushedExpirationTime) ) { performWorkOnRoot( nextFlushedRoot, nextFlushedExpirationTime, !deadlineDidExpire, ); findHighestPriorityRoot(); } } else { while ( nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime) ) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false); findHighestPriorityRoot(); } } if (deadline !== null) { callbackExpirationTime = NoWork; callbackID = -1; } // If there's work left over, schedule a new callback. if (nextFlushedExpirationTime !== NoWork) { scheduleCallbackWithExpiration(nextFlushedExpirationTime); } // Clean-up. deadline = null; deadlineDidExpire = false; finishRendering(); }
若是有上次遺留,直接調用completeRoot進到渲染階段。若是沒有就調renderRoot開始reconcilation階段。 異步任務主要是渲染的時候判斷一下時間,若是沒時間了,先把finishedWork賦給全局,下次循環處理。
function performWorkOnRoot( root: FiberRoot, expirationTime: ExpirationTime, isAsync: boolean, ) { isRendering = true; // Check if this is async work or sync/expired work. if (!isAsync) { // Flush sync work. let finishedWork = root.finishedWork; if (finishedWork !== null) { // This root is already complete. We can commit it. completeRoot(root, finishedWork, expirationTime); } else { root.finishedWork = null; finishedWork = renderRoot(root, expirationTime, false); if (finishedWork !== null) { // We've completed the root. Commit it. completeRoot(root, finishedWork, expirationTime); } } } else { // Flush async work. let finishedWork = root.finishedWork; if (finishedWork !== null) { // This root is already complete. We can commit it. completeRoot(root, finishedWork, expirationTime); } else { root.finishedWork = null; finishedWork = renderRoot(root, expirationTime, true); if (finishedWork !== null) { // We've completed the root. Check the deadline one more time // before committing. if (!shouldYield()) { // Still time left. Commit the root. completeRoot(root, finishedWork, expirationTime); } else { // There's no time left. Mark this root as complete. We'll come // back and commit it later. root.finishedWork = finishedWork; } } } } isRendering = false; }
若是是第一次進入,會建立一個nextUnitOfWork。 nextUnitOfWork是每一個工做的粒度。 而後調用workLoop
function renderRoot( root: FiberRoot, expirationTime: ExpirationTime, isAsync: boolean, ): Fiber | null { isWorking = true; // Check if we're starting from a fresh stack, or if we're resuming from // previously yielded work. if ( expirationTime !== nextRenderExpirationTime || root !== nextRoot || nextUnitOfWork === null ) { // Reset the stack and start working from the root. resetStack(); nextRoot = root; nextRenderExpirationTime = expirationTime; nextUnitOfWork = createWorkInProgress( nextRoot.current, null, nextRenderExpirationTime, ); root.pendingCommitExpirationTime = NoWork; } let didFatal = false; startWorkLoopTimer(nextUnitOfWork); do { try { workLoop(isAsync); } catch (thrownValue) { // ... } break; } while (true); // We're done performing work. Time to clean up. // ... }
異步任務在處理的時候會調用shouldYield,shouldYield會判斷是否是已經超時了,超時暫時先不作。
function workLoop(isAsync) { if (!isAsync) { // Flush all expired work. while (nextUnitOfWork !== null) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } else { // Flush asynchronous work until the deadline runs out of time. while (nextUnitOfWork !== null && !shouldYield()) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } } function shouldYield() { if (deadline === null) { return false; } if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) { // Disregard deadline.didTimeout. Only expired work should be flushed // during a timeout. This path is only hit for non-expired work. return false; } deadlineDidExpire = true; return true; }
reconcilation又分兩步 1是beginWork,beginWork會開始處理組件,針對不一樣組件不一樣處理。包括dom diff 2 是completeUnitOfWork,completeUnitOfWork會對begin work產生的effect list進行一些處理。
function performUnitOfWork(workInProgress: Fiber): Fiber | null { const current = workInProgress.alternate; startWorkTimer(workInProgress); let next = beginWork(current, workInProgress, nextRenderExpirationTime); if (next === null) { next = completeUnitOfWork(workInProgress); } ReactCurrentOwner.current = null; return next; }
主要是對react 組件進行一些操做。和調用一些生命週期, 咱們主要關注classComponent,就是react的組件 HostConponent在瀏覽器下就是dom
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { if ( workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime ) { return bailoutOnLowPriority(current, workInProgress); } switch (workInProgress.tag) { case FunctionalComponent: return updateFunctionalComponent(current, workInProgress); case ClassComponent: return updateClassComponent( current, workInProgress, renderExpirationTime, ); case HostRoot: return updateHostRoot(current, workInProgress, renderExpirationTime); case HostComponent: return updateHostComponent( current, workInProgress, renderExpirationTime, ); case HostText: return updateHostText(current, workInProgress); case ForwardRef: return updateForwardRef(current, workInProgress); case Fragment: return updateFragment(current, workInProgress); case Mode: return updateMode(current, workInProgress); case ContextProvider: return updateContextProvider( current, workInProgress, renderExpirationTime, ); case ContextConsumer: return updateContextConsumer( current, workInProgress, renderExpirationTime, ); default: invariant( false, 'Unknown unit of work tag. This error is likely caused by a bug in ' + 'React. Please file an issue.', ); } }
mount組件,構建組件實例,調用生命週期好比willMount,初始化組件的的updateQueue。
function updateClassComponent( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ) { // Push context providers early to prevent context stack mismatches. // During mounting we don't know the child context yet as the instance doesn't exist. // We will invalidate the child context in finishClassComponent() right after rendering. const hasContext = pushLegacyContextProvider(workInProgress); let shouldUpdate; if (current === null) { if (workInProgress.stateNode === null) { // In the initial pass we might need to construct the instance. constructClassInstance(workInProgress, workInProgress.pendingProps); mountClassInstance(workInProgress, renderExpirationTime); shouldUpdate = true; } else { // In a resume, we'll already have an instance we can reuse. shouldUpdate = resumeMountClassInstance( workInProgress, renderExpirationTime, ); } } else { shouldUpdate = updateClassInstance( current, workInProgress, renderExpirationTime, ); } let didCaptureError = false; const updateQueue = workInProgress.updateQueue; if (updateQueue !== null && updateQueue.capturedValues !== null) { shouldUpdate = true; didCaptureError = true; } return finishClassComponent( current, workInProgress, shouldUpdate, hasContext, didCaptureError, renderExpirationTime, ); }
finishClassComponent會調用reconcileChildFibers進行dom diff。
function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, expirationTime: ExpirationTime, ): Fiber | null { if ( typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null ) { newChild = newChild.props.children; } // Handle object types const isObject = typeof newChild === 'object' && newChild !== null; if (isObject) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, expirationTime, ), ); case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, expirationTime, ), ); } } if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, expirationTime, ), ); } if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, expirationTime, ); } if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, expirationTime, ); } }
大部分狀況是reconcileChildrenArray,就那這個來講。
function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array<*>, expirationTime: ExpirationTime, ): Fiber | null { let resultingFirstChild: Fiber | null = null; let previousNewFiber: Fiber | null = null; let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { // 沒有采用兩端同時對比,受限於Fiber列表的單向結構 if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } const newFiber = updateSlot( // 生成新的fiber returnFiber, oldFiber, newChildren[newIdx], expirationTime, ); //若是在遍歷中發現key值不相等的狀況,則直接跳出第一輪遍歷 if (newFiber === null) { if (oldFiber === null) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && newFiber.alternate === null) { // 咱們找到了匹配的節點,但咱們並不保留當前的Fiber,因此咱們須要刪除當前的子節點 // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); // 記錄上一個更新的子節點 if (previousNewFiber === null) { resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } if (newIdx === newChildren.length) { // 咱們已經遍歷完了全部的新節點,直接刪除剩餘舊節點 // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } if (oldFiber === null) { // 若是舊節點先遍歷完,則按順序插入剩餘的新節點 // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild( returnFiber, newChildren[newIdx], expirationTime, ); if (!newFiber) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } return resultingFirstChild; } // 把子節點都設置快速查找的map映射集 const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // 使用map查找須要保存或刪除的節點 for (; newIdx < newChildren.length; newIdx++) { const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], expirationTime, ); if (newFiber) { if (shouldTrackSideEffects) { if (newFiber.alternate !== null) { // 新的Fiber也是一個工做線程,可是若是已有當前的實例,那咱們就能夠複用這個Fiber, // 咱們要從Map中刪除這個新的,避免準備複用的Fiber被刪除 existingChildren.delete( newFiber.key === null ? newIdx : newFiber.key, ); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; } } if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. // 到此全部剩餘的Map的節點都將被刪除,加入刪除隊列 existingChildren.forEach(child => deleteChild(returnFiber, child)); } // 最終返回Fiber子節點列表的第一個節點 return resultingFirstChild; }
能夠看到其實刪除節點並非直接刪除而是打個Deletion的tag。生成effect list
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void { const last = returnFiber.lastEffect; if (last !== null) { last.nextEffect = childToDelete; returnFiber.lastEffect = childToDelete; } else { returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; } childToDelete.nextEffect = null; childToDelete.effectTag = Deletion; }
在dom diff以後會有一個收尾工做大概就是effect的各類處理,就是workLoop以後的completeUnitOfWork函數。 同步effect list到 current 的host root 樹。 調用completeWork
function completeUnitOfWork(workInProgress: Fiber): Fiber | null { while (true) { const current = workInProgress.alternate; const returnFiber = workInProgress.return; const siblingFiber = workInProgress.sibling; if ((workInProgress.effectTag & Incomplete) === NoEffect) { // This fiber completed. let next = completeWork( current, workInProgress, nextRenderExpirationTime, ); stopWorkTimer(workInProgress); resetExpirationTime(workInProgress, nextRenderExpirationTime); if (next !== null) { stopWorkTimer(workInProgress); if (__DEV__ && ReactFiberInstrumentation.debugTool) { ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress); } // If completing this work spawned new work, do that next. We'll come // back here again. return next; } // 將當前fiber子樹上的effect list 插入到當前hostRoot 樹的effectlist中 if ( returnFiber !== null && // Do not append effects to parents if a sibling failed to complete (returnFiber.effectTag & Incomplete) === NoEffect ) { // 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. if (returnFiber.firstEffect === null) { returnFiber.firstEffect = workInProgress.firstEffect; } if (workInProgress.lastEffect !== null) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = workInProgress.firstEffect; } returnFiber.lastEffect = workInProgress.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 effectTag = workInProgress.effectTag; // Skip both NoWork and PerformedWork tags when creating the effect list. // PerformedWork effect is read by React DevTools but shouldn't be committed. if (effectTag > PerformedWork) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = workInProgress; } else { returnFiber.firstEffect = workInProgress; } returnFiber.lastEffect = workInProgress; } } if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. return siblingFiber; } else if (returnFiber !== null) { // If there's no more work in this returnFiber. Complete the returnFiber. workInProgress = returnFiber; continue; } else { // We've reached the root. isRootReadyForCommit = true; return null; } } 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(workInProgress); // Because this fiber did not complete, don't reset its expiration time. if (workInProgress.effectTag & DidCapture) { // Restarting an error boundary stopFailedWorkTimer(workInProgress); } else { stopWorkTimer(workInProgress); } if (next !== null) { stopWorkTimer(workInProgress); if (__DEV__ && ReactFiberInstrumentation.debugTool) { ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress); } // 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.effectTag &= HostEffectMask; return next; } if (returnFiber !== null) { // Mark the parent fiber as incomplete and clear its effect list. returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.effectTag |= Incomplete; } if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. return siblingFiber; } else if (returnFiber !== null) { // If there's no more work in this returnFiber. Complete the returnFiber. workInProgress = returnFiber; continue; } else { return null; } } } return null; }
比較長,不貼代碼了。主要作的事情就是根據不一樣的component類型進行不一樣的處理。 重點是對HostComponent的props進行diff,並標記更新。 若是是react首次渲染,調用createInstance建立一個HostComponent。 若是已經存在HostComponent,檢查節點是否須要更新,調用prepareUpdate,進行diff dom屬性。
到此reconciliation階段結束,主要負責產出effect list。 能夠說reconcile的過程至關因而一個純函數,輸入是fiber節點,輸出一個effect list。
由於純函數的可預測性,讓咱們能夠隨時中斷reconciliation階段的執行,而不用擔憂side-effects給讓組件狀態和實際UI產生不一致
function commitRoot(finishedWork: Fiber): ExpirationTime { isWorking = true; isCommitting = true; startCommitTimer(); const root: FiberRoot = finishedWork.stateNode; const committedExpirationTime = root.pendingCommitExpirationTime; root.pendingCommitExpirationTime = NoWork; const currentTime = recalculateCurrentTime(); // Reset this to null before calling lifecycles ReactCurrentOwner.current = null; let firstEffect; if (finishedWork.effectTag > PerformedWork) { // fiber的effect list只包括它子樹中的effects,將節點的effect加到effect list鏈表中 if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // There is no effect on the root. firstEffect = finishedWork.firstEffect; } // 作一些dom事件相關設置 prepareForCommit(root.containerInfo); // Commit all the side-effects within a tree. We'll do this in two passes. // The first pass performs all the host insertions, updates, deletions and // ref unmounts. nextEffect = firstEffect; startCommitHostEffectsTimer(); while (nextEffect !== null) { let didError = false; let error; try { // 遍歷fiber樹的effect list,調用相關的生命週期,好比willUnmount。操做dom,完成渲染。 commitAllHostEffects(); } catch (e) { didError = true; error = e; } } stopCommitHostEffectsTimer(); resetAfterCommit(root.containerInfo); root.current = finishedWork; nextEffect = firstEffect; startCommitLifeCyclesTimer(); while (nextEffect !== null) { let didError = false; let error; try { // 再遍歷effect list,若是effect發生在classComponent上,加調didMount和didUpdate方法。 // 若是發生在hostComponents上,會調用commitMount方法,主要是爲了在render一個節點渲染以後作一些操做。好比input的auto-focus。 commitAllLifeCycles(root, currentTime, committedExpirationTime); } catch (e) { didError = true; error = e; } } isCommitting = false; isWorking = false; stopCommitLifeCyclesTimer(); stopCommitTimer(); if (typeof onCommitRoot === 'function') { onCommitRoot(finishedWork.stateNode); } const remainingTime = root.current.expirationTime; if (remainingTime === NoWork) { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; } return remainingTime; }