前言
本文的React代码版本为18.2.0
可调试的代码仓库为:GitHub - yyyao-hh/react-debug at master-pure
在React的渲染流程中,Commit阶段是将虚拟DOM变更同步到真实DOM的关键环节。如果说Reconciliation(协调)阶段是React的"思考"过程,那么Commit阶段就是React的"执行"过程。本文将深入React 18源码,完整解析从Fiber树到真实DOM的同步链路。
Commit 阶段的入口
入口分析部分和【构造Fiber树】一文的链路比较相似,可以选择跳过
我们从入口文件开始分析:首先创建一个根容器,然后对应用进行挂载
/* index.js */
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
然后在render方法的内部调用了updateContainer方法启动了渲染流程
/* react/packages/react-dom/src/client/ReactDOMRoot.js */
ReactDOMRoot.prototype.render = function(children: ReactNodeList) {
const root = this._internalRoot; // 根节点
updateContainer(children, root, null, null);
};
/* react/packages/react-reconciler/src/ReactFiberReconciler.old.js */
export function updateContainer(...) {
const root = enqueueUpdate(current, update, lane);
scheduleUpdateOnFiber(root, current, lane, eventTime);
}
/* react/packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
export function scheduleUpdateOnFiber(...) {
// 注册调度任务, 之后由 Scheduler 调度, 构造 Fiber 树
ensureRootIsScheduled(root, eventTime);
}
ensureRootIsScheduled函数中通过scheduleCallback、scheduleSyncCallback、scheduleLegacySyncCallback等方法注册了两种类型的调度任务。然后调度任务中就是Fiber树的构建逻辑。
- 同步渲染:
performSyncWorkOnRoot - 并发渲染:
performConcurrentWorkOnRoot
/* react/packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
function ensureRootIsScheduled(...) {
// 1. 同步任务处理
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
// 2. 并发任务处理
} else {
// ...计算优先级的逻辑
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
}
而commit阶段也是从performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用开始。为什么说“也”呢,因为render阶段也来自这两个方法的调用。
当我们执行完renderRootSync或renderRootConcurrent方法后,就构建好了Fiber树,最终都会调用本章的重点:commitRoot方法
performSyncWorkOnRoot => commitRootperformConcurrentWorkOnRoot => finishConcurrentRender => commitRoot
/* packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
function performSyncWorkOnRoot(root) {
// 执行副作用
flushPassiveEffects();
// 构建 Fiber 树
renderRootSync(root, lanes);
// 进入 Commit 阶段
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
// 调度循环
ensureRootIsScheduled(root, now());
return null;
}
/* packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
function performConcurrentWorkOnRoot(root, didTimeout) {
// 执行副作用
flushPassiveEffects();
// 构建 Fiber 树
shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
// 进入 Commit 阶段
finishConcurrentRender(root, exitStatus, lanes);
// 调度循环
ensureRootIsScheduled(root, now());
// 返回延续任务或 null
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
case RootInProgress:
case RootFatalErrored: {
throw new Error('Root did not complete. This is a bug in React.');
}
case RootErrored: {
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
case RootSuspended: {
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
case RootSuspendedWithDelay: {
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
case RootCompleted: {
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
default: {
throw new Error('Unknown root exit status.');
}
}
}
Commit 阶段整体架构
Commit阶段从commitRoot开始,这是render阶段结束后的第一个入口:它又调用了 commitRootImpl 这个函数
/* src/react/packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
function commitRoot(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
) {
commitRootImpl(
root,
recoverableErrors,
transitions,
previousUpdateLanePriority,
);
}
可以看出React将Commit阶段细分为三个子阶段,确保DOM更新的正确性和性能:
- Before Mutation 阶段:读取
DOM信息(如滚动位置) - Mutation 阶段:执行
DOM更新 - Layout 阶段:执行副作用回调
/* src/react/packages/react-reconciler/src/ReactFiberWorkLoop.old.js */
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority,
) {
// 循环执行副作用
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
if (subtreeHasEffects || rootHasEffect) {
// 1. 执行 commitBeforeMutationEffects: DOM 变更前读取状态
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
// 2. 执行 commitMutationEffects: DOM 更新
commitMutationEffects(root, finishedWork, lanes);
// 3. 执行 commitLayoutEffects: 执行布局相关副作用
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);
// 在帧结束时让出控制权, 交给浏览器绘制
requestPaint();
}
// 继续调度, 执行待处理的更新
ensureRootIsScheduled(root, now());
// 处理提交中产生的同步更新
flushSyncCallbacks();
return null;
}
Before Mutation 阶段
这个阶段的主要任务就是class组件在DOM更新前获取状态快照。
- 入口函数是
commitBeforeMutationEffects,他主要执行一些关键的准备工作,特别是处理与焦点和失焦相关的逻辑。 - 入口函数内部调用了
commitBeforeMutationEffects_begin函数,这个函数内部又调用了commitBeforeMutationEffects_complete函数,二者共同实现了深度优先遍历。通过这种遍历方式,能够确保组件生命周期方法(如getSnapshotBeforeUpdate)按照从子组件到父组件的顺序调用:
-
commitBeforeMutationEffects_begin负责向下遍历,直到找到需要处理副作用的叶子节点或子树没有副作用的节点。commitBeforeMutationEffects_complete负责处理当前节点,然后寻找兄弟节点,如果没有兄弟节点则回溯到父节点。
- 在“归”方法中又调用了
commitBeforeMutationEffectsOnFiber函数,处理带有Snapshot标志的Fiber节点,其中主要是ClassComponent的getSnapshotBeforeUpdate生命周期方法,以及HostRoot的清空容器操作。
这块的递归逻辑和构建Fiber树一文的递归逻辑相似,函数命名也是,有兴趣的可以回头看看
/* src/react/packages/react-reconciler/src/ReactFiberCommitWork.old.js */
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
) {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
const shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
function commitBeforeMutationEffects_begin() {
// 深度优先遍历Fiber树, 寻找并处理带有BeforeMutation副作用的节点
while (nextEffect !== null) {
const fiber = nextEffect;
if (enableCreateEventHandleAPI) {
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
// 确保被删除的组件能正确触发 blur 事件
commitBeforeMutationEffectsDeletion(deletion);
}
}
}
// 当前节点的子树中有需要处理的副作用, 则继续向下遍历
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
const sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
break;
}
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
break;
default: {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
resetCurrentDebugFiberInDEV();
}
}
Mutation 阶段
这个阶段我们会将修改同步到真实DOM上了。每种Fiber类型的处理都遵循相同模式:
recursivelyTraverseMutationEffects:深度优先遍历,优先处理删除逻辑commitReconciliationEffects:处理节点插入/移动的关键环节
/* src/react/packages/react-reconciler/src/ReactFiberCommitWork.old.js */
export function commitMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
setCurrentDebugFiberInDEV(finishedWork);
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
setCurrentDebugFiberInDEV(finishedWork);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
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);
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
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);
}
}
}
return;
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
return;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
if (supportsMutation) {
if (finishedWork.flags & ContentReset) {
const instance: Instance = finishedWork.stateNode;
try {
resetTextContent(instance);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
if (flags & Update) {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps =
current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
try {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
} catch (error) {
captureCommitPhaseError(
finishedWork,
finishedWork.return,
error,
);
}
}
}
}
}
return;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
if (supportsMutation) {
if (finishedWork.stateNode === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
const oldText: string =
current !== null ? current.memoizedProps : newText;
try {
commitTextUpdate(textInstance, oldText, newText);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
if (supportsMutation && supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
try {
commitHydratedContainer(root.containerInfo);
} catch (error) {
captureCommitPhaseError(
finishedWork,
finishedWork.return,
error,
);
}
}
}
}
if (supportsPersistence) {
const containerInfo = root.containerInfo;
const pendingChildren = root.pendingChildren;
try {
replaceContainerChildren(containerInfo, pendingChildren);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
case HostPortal: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
if (supportsPersistence) {
const portal = finishedWork.stateNode;
const containerInfo = portal.containerInfo;
const pendingChildren = portal.pendingChildren;
try {
replaceContainerChildren(containerInfo, pendingChildren);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
case SuspenseComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
const offscreenFiber: Fiber = (finishedWork.child: any);
if (offscreenFiber.flags & Visibility) {
const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
const newState: OffscreenState | null = offscreenFiber.memoizedState;
const isHidden = newState !== null;
// Track the current state on the Offscreen instance so we can
// read it during an event
offscreenInstance.isHidden = isHidden;
if (isHidden) {
const wasHidden =
offscreenFiber.alternate !== null &&
offscreenFiber.alternate.memoizedState !== null;
if (!wasHidden) {
// TODO: Move to passive phase
markCommitTimeOfFallback();
}
}
}
if (flags & Update) {
try {
commitSuspenseCallback(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
attachSuspenseRetryListeners(finishedWork);
}
return;
}
case OffscreenComponent: {
const wasHidden = current !== null && current.memoizedState !== null;
if (
enableSuspenseLayoutEffectSemantics &&
finishedWork.mode & ConcurrentMode
) {
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
}
commitReconciliationEffects(finishedWork);
if (flags & Visibility) {
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const offscreenBoundary: Fiber = finishedWork;
offscreenInstance.isHidden = isHidden;
if (enableSuspenseLayoutEffectSemantics) {
if (isHidden) {
if (!wasHidden) {
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
nextEffect = offscreenBoundary;
let offscreenChild = offscreenBoundary.child;
while (offscreenChild !== null) {
nextEffect = offscreenChild;
disappearLayoutEffects_begin(offscreenChild);
offscreenChild = offscreenChild.sibling;
}
}
}
} else {
if (wasHidden) {
// TODO: Move re-appear call here for symmetry?
}
}
}
if (supportsMutation) {
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
}
}
return;
}
case SuspenseListComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
attachSuspenseRetryListeners(finishedWork);
}
return;
}
case ScopeComponent: {
if (enableScopeAPI) {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
safelyDetachRef(finishedWork, finishedWork.return);
}
safelyAttachRef(finishedWork, finishedWork.return);
}
if (flags & Update) {
const scopeInstance = finishedWork.stateNode;
prepareScopeUpdate(scopeInstance, finishedWork);
}
}
return;
}
default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
return;
}
}
}
Layout 阶段
- 它主要负责在
DOM变更之后同步执行一些副作用,比如类组件的componentDidMount和componentDidUpdate,函数组件的useLayoutEffect - 以及一些宿主组件(如
DOM元素)的特定操作(如自动聚焦) - 此外,它还处理了
ref的附着
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (
!enableSuspenseLayoutEffectSemantics ||
!offscreenSubtreeWasHidden
) {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(
HookLayout | HookHasEffect,
finishedWork,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
}
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
}
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(
finishedWork.type,
current.memoizedProps,
);
const prevState = current.memoizedState;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
}
}
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot: {
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
break;
}
case HostText: {
break;
}
case HostPortal: {
break;
}
case Profiler: {
if (enableProfilerTimer) {
const {onCommit, onRender} = finishedWork.memoizedProps;
const {effectDuration} = finishedWork.stateNode;
const commitTime = getCommitTime();
let phase = current === null ? 'mount' : 'update';
if (enableProfilerNestedUpdatePhase) {
if (isCurrentUpdateNested()) {
phase = 'nested-update';
}
}
if (typeof onRender === 'function') {
onRender(
finishedWork.memoizedProps.id,
phase,
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
);
}
if (enableProfilerCommitHooks) {
if (typeof onCommit === 'function') {
onCommit(
finishedWork.memoizedProps.id,
phase,
effectDuration,
commitTime,
);
}
enqueuePendingPassiveProfilerEffect(finishedWork);
let parentFiber = finishedWork.return;
outer: while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
const root = parentFiber.stateNode;
root.effectDuration += effectDuration;
break outer;
case Profiler:
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break outer;
}
parentFiber = parentFiber.return;
}
}
}
break;
}
case SuspenseComponent: {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
break;
}
case SuspenseListComponent:
case IncompleteClassComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
case TracingMarkerComponent: {
break;
}
default:
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
if (!enableSuspenseLayoutEffectSemantics || !offscreenSubtreeWasHidden) {
if (enableScopeAPI) {
if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
commitAttachRef(finishedWork);
}
} else {
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
}
}
总结
通过深入分析Commit阶段源码,我们可以看到:
- 三阶段架构:
Before Mutation → Mutation → Layout的分阶段设计确保了DOM更新的正确顺序 - 批量更新:
React通过effect链表和优先级调度实现了高效的批量更新 - 错误恢复:完善的错误边界机制确保局部UI错误不会导致整个应用崩溃
- 性能优化:
DOM操作的批量执行和最小化变更减少了浏览器重排重绘
Commit阶段作为React渲染流水线的最后一环,其设计体现了React在性能、稳定性和开发者体验之间的精妙平衡。理解这一过程不仅有助于编写高性能的React应用,更能让我们深入理解现代前端框架的设计哲学。