React-Commit阶段解析
Howard 11/24/2023 React
React 的 commit
阶段是整个渲染流程中的最后一个阶段,它负责将所有的变更应用到真实的 DOM 中,同时执行一些生命周期函数、Hooks 的回调,以及处理事件系统的绑定。在这个阶段,React 执行了一系列的任务来确保更新的正确应用。
# 主要任务
- 执行生命周期函数和 Hooks 的回调: 这包括 FunctionComponent、ForwardRef、SimpleMemoComponent 等的
useLayoutEffect
和useEffect
回调,以及 ClassComponent 的componentDidMount
和componentDidUpdate
生命周期。 - 提交 Fiber 的修改到浏览器 DOM 中: 通过
commitReconciliationEffects
函数将 Fiber 树上的修改同步到浏览器的 DOM 中。 - 事件系统的绑定: 为 HostComponent 注册事件,将整个
props
记录下来,等待事件分发的时候执行回调。 - 再次提交渲染: 通过
ensureRootIsScheduled
函数再次调度 React 的渲染,执行下一轮的渲染。
# 具体执行流程
commitMutationEffects
函数: 首先提交浏览器 DOM 的修改,包括执行commitReconciliationEffects
但因为 javascript 还在执行,所以浏览器的渲染被阻塞住。recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork); if (flags & Update) { try { commitHookEffectListUnmount( HookInsertion | HookHasEffect, finishedWork, finishedWork.return ); commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork); } catch (error) { captureCommitPhaseError(finishedWork, finishedWork.return, error); } // Layout effects are destroyed during the mutation phase so that all // destroy functions for all fibers are called before any create functions. // This prevents sibling component effects from interfering with each other, // e.g. a destroy function in one component should never override a ref set // by a create function in another component during the same commit. if (shouldProfile(finishedWork)) { try { startLayoutEffectTimer(); commitHookEffectListUnmount( HookLayout | HookHasEffect, finishedWork, finishedWork.return ); } catch (error) { captureCommitPhaseError(finishedWork, finishedWork.return, error); } recordLayoutEffectDuration(finishedWork); } else { try { commitHookEffectListUnmount( HookLayout | HookHasEffect, finishedWork, finishedWork.return ); } catch (error) { captureCommitPhaseError(finishedWork, finishedWork.return, error); } } }
commitLayoutEffects
函数: 执行useLayoutEffect
和 ClassComponent 的生命周期函数。switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: { recursivelyTraverseLayoutEffects( finishedRoot, finishedWork, committedLanes, ); if (flags & Update) { commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect); } break; } case ClassComponent: { recursivelyTraverseLayoutEffects( finishedRoot, finishedWork, committedLanes, ); if (flags & Update) { commitClassLayoutLifecycles(finishedWork, current); } if (flags & Callback) { commitClassCallbacks(finishedWork); } if (flags & Ref) { safelyAttachRef(finishedWork, finishedWork.return); } break; } ... }
flushPassiveEffects
函数: 在浏览器 DOM 渲染完毕后执行,用于执行 FunctionComponent 的useEffect
回调。commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current, lanes, transitions);
ensureRootIsScheduled
函数: Commit 阶段在最后的阶段再调度一次 React 的渲染,去执行下一次的渲染流程。
# 结语
React 的 commit
阶段是整个渲染流程的收尾,它确保了变更的正确应用和一些副作用的执行。深入理解这个阶段对于优化和调试 React 应用非常重要。