前言
commitRoot 方法是 commit
阶段工作的起点。
function commitRoot(root) {
// TODO: This no longer makes any sense. We already wrap the mutation and
// layout phases. Should be able to remove.
const previousUpdateLanePriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
try {
ReactCurrentBatchConfig.transition = 0;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
commitRoot 方法源码:github.com/facebook/re…
commitRoot(root)方法中的 root 参数 FiberRootNode
。
调度 useEffect
useEffect 的函数会在浏览器完成布局与绘制之后,在 scheduleCallback
一个延迟事件中被调用。在 commitRootImpl
方法中会异步调度回调函数 flushPassiveEffects
方法,它会触发useEffect
方法。
可以在 这里 看到源码
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
// 触发useEffect
flushPassiveEffects();
return null;
});
}
}
commit 阶段的主要内容
commit 阶段的主要工作(即 Renderer
的工作流程)分为三部分:
-
before mutation
阶段(执行 DOM 操作前) -
mutation
阶段(执行 DOM 操作) -
layout
阶段(执行 DOM 操作后)
before mutation 阶段
可以在 这里 看到部分源码
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 0;
// 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
// 将当前上下文标记为CommitContext,作为commit阶段的标志
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
ReactCurrentOwner.current = null;
// beforeMutation 阶段的主函数
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
.....
before Mutation 阶段的主函数是 commitBeforeMutationEffects
。
可以在 这里 看到源码
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
) {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
// 遍历effectList
commitBeforeMutationEffects_begin();
const shouldFire = shouldFireAfterActiveInstanceBlur;
// 处理focus状态
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
commitBeforeMutationEffects_begin
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
commitBeforeMutationEffectsDeletion(deletion);
}
}
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
fiber.subtreeFlags
表示子树包含的副作用标识,避免深度遍历。当所有带有副作用的 fiber 节点都遍历完后,会执行 commitBeforeMutationEffects_complete 。
commitBeforeMutationEffects_complete
commitBeforeMutationEffects_complete 方法中对于每个 fiber 节点都会调用 commitBeforeMutationEffectsOnFiber
方法。
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
if (__DEV__) {
.....
} else {
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
commitBeforeMutationEffectsOnFiber
在 commitBeforeMutationEffectsOnFiber
方法中会对于 ClassComponent 会调用 getSnapshotBeforeUpdate
生命周期方法。可以在 这里 看到源码。
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
if (
finishedWork.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
doesFiberContain(finishedWork, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(finishedWork);
}
}
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(finishedWork);
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;
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default: {
invariant(
false,
'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();
}
}
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
从 v16.3.0
开始 componentWillMount
、componentWillRecieveProps
、componentWillUpdate
三个生命周期钩子被标记为 UNSAFE
。
被标记为 UNSAFE 是因为 Stack Reconciler
重构为 Fiber Reconciler
后,render 阶段的任务可能中断/重新开始,componentWillXXX
生命周期钩子可能触发多次。
而 getSnapshotBeforeUpdate
是在 commit
阶段内的 before mutation
阶段调用的,由于commit 阶段是同步的,只会调用一次。
mutation 阶段
mutation
阶段的主函数是 commitMutationEffects。
源码位置: github.com/facebook/re…
commitMutationEffects(root, finishedWork, lanes);
其中参数 root 代表了整个应用的根节点 FiberRootNode
,finishedWork 代表是所在组件树的根节点 rootFiber
,lanes 表示优先级相关。
commitMutationEffects
你可以在 这里 看到 commitMutationEffects 源码
export function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
commitMutationEffects_begin(root);
inProgressLanes = null;
inProgressRoot = null;
}
从上面的代码中我们可以看到该方法会调用 commitMutationEffects_begin
方法。
commitMutationEffects_begin
function commitMutationEffects_begin(root: FiberRoot) {
// 遍历 effectList
while (nextEffect !== null) {
const fiber = nextEffect;
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
if (__DEV__) {
....
} else {
try {
commitDeletion(root, childToDelete, fiber);
} catch (error) {
captureCommitPhaseError(childToDelete, fiber, error);
}
}
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
// 遍历完成
commitMutationEffects_complete(root);
}
}
}
commitMutationEffects_begin 会遍历带有 effectTag 的 effectList。当 Fiber 节点含有 Deletion effectTag,调用 commitDeletion
方法删除。
Deletion effect
function commitDeletion(
finishedRoot: FiberRoot,
current: Fiber,
nearestMountedAncestor: Fiber,
): void {
if (supportsMutation) {
unmountHostComponents(finishedRoot, current, nearestMountedAncestor);
} else {
commitNestedUnmounts(finishedRoot, current, nearestMountedAncestor);
}
detachFiberMutation(current);
}
最终会调用有 commitUnmount
方法
可以从 这里 看到源码
function commitUnmount(
finishedRoot: FiberRoot,
current: Fiber,
nearestMountedAncestor: Fiber,
): void {
onCommitUnmount(current);
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {destroy, tag} = effect;
if (destroy !== undefined) {
if ((tag & HookLayout) !== NoHookEffect) {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
current.mode & ProfileMode
) {
startLayoutEffectTimer();
safelyCallDestroy(current, nearestMountedAncestor, destroy);
recordLayoutEffectDuration(current);
} else {
safelyCallDestroy(current, nearestMountedAncestor, destroy);
}
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
return;
}
case ClassComponent: {
safelyDetachRef(current, nearestMountedAncestor);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
current,
nearestMountedAncestor,
instance,
);
}
return;
}
case HostComponent: {
safelyDetachRef(current, nearestMountedAncestor);
return;
}
case HostPortal: {
if (supportsMutation) {
unmountHostComponents(finishedRoot, current, nearestMountedAncestor);
} else if (supportsPersistence) {
emptyPortalContainer(current);
}
return;
}
case DehydratedFragment: {
if (enableSuspenseCallback) {
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
const onDeleted = hydrationCallbacks.onDeleted;
if (onDeleted) {
onDeleted((current.stateNode: SuspenseInstance));
}
}
}
return;
}
case ScopeComponent: {
if (enableScopeAPI) {
safelyDetachRef(current, nearestMountedAncestor);
}
return;
}
}
}
该方法主要工作:
-
递归调用 Fiber 节点及其子孙 Fiber 节点中 fiber.tag 为
ClassComponent
的componentWillUnmount
生命周期钩子,从页面移除Fiber节点对应DOM节点; -
解绑 ref 属性;
-
调度
FunctionComponent
useEffect 的销毁函数;
当所有带有副作用的 fiber 节点都遍历完后,会执行 commitMutationEffects_complete 方法。
commitMutationEffects_complete
function commitMutationEffects_complete(root: FiberRoot) {
// 遍历effectList
while (nextEffect !== null) {
const fiber = nextEffect;
if (__DEV__) {
.....
} else {
try {
commitMutationEffectsOnFiber(fiber, root);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
在 commitMutationEffects_complete 方法中会执行 commitMutationEffectsOnFiber
方法。
commitMutationEffectsOnFiber
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
const flags = finishedWork.flags;
// 根据 ContentReset effectTag重置文字节点
if (flags & ContentReset) {
commitResetTextContent(finishedWork);
}
// 更新ref
if (flags & Ref) {
const current = finishedWork.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
if (finishedWork.tag === ScopeComponent) {
commitAttachRef(finishedWork);
}
}
}
// 根据 effectTag 分别处理
const primaryFlags = flags & (Placement | Update | Hydrating);
outer: switch (primaryFlags) {
// 插入DOM
case Placement: {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
break;
}
// 插入DOM 并 更新DOM
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
// SSR 相关
case Hydrating: {
finishedWork.flags &= ~Hydrating;
break;
}
// SSR 相关
case HydratingAndUpdate: {
finishedWork.flags &= ~Hydrating;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
// 更新 DOM
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
}
}
commitMutationEffectsOnFiber 对每个带有 effectTag
的 Fiber 节点执行如下三个操作:
- 根据 ContentReset effectTag 重置文字节点;
- 更新 ref 属性;
- 根据 effectTag 分别处理,其中 effectTag 包括(Placement | Update | Hydrating | HydratingAndUpdate)
Placement effect
当 Fiber节点含有 Placement effectTag,表示该 Fiber 节点对应的 DOM 节点需要插入到页面中,会调用的 commitPlacement 方法。
commitPlacement 方法 源码
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
const parentFiber = getHostParentFiber(finishedWork);
let parent;
let isContainer;
const parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
default:
invariant(
false,
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
if (parentFiber.flags & ContentReset) {
resetTextContent(parent);
parentFiber.flags &= ~ContentReset;
}
const before = getHostSibling(finishedWork);
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
}
该方法所做的工作分为三步:
- 获取父级 DOM 节点
const parentFiber = getHostParentFiber(finishedWork);
- 获取 Fiber 节点的 DOM 兄弟节点
const before = getHostSibling(finishedWork);
- 根据 parentStateNode 是否是 rootFiber 来调用 parentNode.insertBefore 或者parentNode.appendChild 执行 DOM 插入操作。
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
- insertOrAppendPlacementNodeIntoContainer 方法
function insertOrAppendPlacementNodeIntoContainer(
node: Fiber,
before: ?Instance,
parent: Container,
): void {
const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
appendChildToContainer(parent, stateNode);
}
} else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
- insertOrAppendPlacementNode 方法
function insertOrAppendPlacementNode(
node: Fiber,
before: ?Instance,
parent: Instance,
): void {
const {tag} = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost) {
const stateNode = node.stateNode;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
let sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
这两个方法都会根据 Fiber 节点的 DOM 兄弟节点是否存在来执行 parentNode.appendChild
方法或者 parentNode.insertBefore
方法。
Update effect
当 Fiber 节点含有 Update effectTag,意味着该 Fiber 节点需要更新,此时调用 commitWork
方法。
commitWork 源码
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
.......
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
}
return;
}
case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
invariant(
finishedWork.stateNode !== null,
'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;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
if (supportsHydration) {
const root: FiberRoot = finishedWork.stateNode;
if (root.hydrate) {
// We've just hydrated. No need to hydrate again.
root.hydrate = false;
commitHydratedContainer(root.containerInfo);
}
}
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
commitSuspenseComponent(finishedWork);
attachSuspenseRetryListeners(finishedWork);
return;
}
case SuspenseListComponent: {
attachSuspenseRetryListeners(finishedWork);
return;
}
case IncompleteClassComponent: {
return;
}
case ScopeComponent: {
if (enableScopeAPI) {
const scopeInstance = finishedWork.stateNode;
prepareScopeUpdate(scopeInstance, finishedWork);
return;
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
hideOrUnhideAllChildren(finishedWork, isHidden);
return;
}
}
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
- FunctionComponent
当 fiber.tag 为 FunctionComponent 时,会调用 commitHookEffectListUnmount
方法。该方法会遍历 effectList ,执行所有 useLayoutEffect hook 的销毁函数。
commitHookEffectListUnmount 源码
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
- HostComponent
当 fiber.tag 为 HostComponent 时,会调用 commitUpdate
方法。
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
commitUpdate 源码
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
updateFiberProps(domElement, newProps);
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
可以看出 commitUpdate 会调用 updateProperties
方法更新 DOM 属性。
layout 阶段
layout 阶段的代码都是在 DOM 渲染完成( mutation 阶段完成)后执行的,在执行 layout 阶段的代码之前会执行 root.current = finishedWork
;
可以从 这里 看到源码
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
finishedWork
表示已经渲染完成的 workInProgress Fiber
树,根据 Fiber 架构双缓存机制,fiberRootNode
的 current 指针会指向 workInProgress Fiber 树,意味着 workInProgress Fiber 树变成了 current Fiber 树
。
在 mutation 阶段之后 layout 阶段之前执行 root.current = finishedWork 是因为在 mutation 阶段会执行 componentWillUnmount 生命周期,此时 root.current 还指向之前的 Fiber 树。而在 layout 阶段会执行 componentDidMount/Update 两个生命周期函数,此时 root.current 已经指向了 workInProgress Fiber 树。
commitLayoutEffects
layout 阶段会调用 commitLayoutEffects 函数。
可以从 这里 看到源码
export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
在该方法中会执行 commitLayoutEffects_begin
。
commitLayoutEffects_begin
可以从 这里 看到源码
function commitLayoutEffects_begin(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
// Offscreen是一个开发中的API,预计会在某个v18的小版本发布。
// 他的功能类似Vue中的keep-alive,用来在组件「失活」时在后台保存组件状态。
if (enableSuspenseLayoutEffectSemantics && isModernRoot) {
// Keep track of the current Offscreen stack's state.
if (fiber.tag === OffscreenComponent) {
const current = fiber.alternate;
const wasHidden = current !== null && current.memoizedState !== null;
const isHidden = fiber.memoizedState !== null;
const newOffscreenSubtreeIsHidden =
isHidden || offscreenSubtreeIsHidden;
const newOffscreenSubtreeWasHidden =
wasHidden || offscreenSubtreeWasHidden;
if (
newOffscreenSubtreeIsHidden !== offscreenSubtreeIsHidden ||
newOffscreenSubtreeWasHidden !== offscreenSubtreeWasHidden
) {
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
// Traverse the Offscreen subtree with the current Offscreen as the root.
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
commitLayoutEffects_begin(
fiber, // New root; bubble back up to here and stop.
root,
committedLanes,
);
// Restore Offscreen state and resume in our-progress traversal.
nextEffect = fiber;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
continue;
}
}
}
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
if (enableSuspenseLayoutEffectSemantics && isModernRoot) {
const visibilityChanged =
!offscreenSubtreeIsHidden && offscreenSubtreeWasHidden;
// TODO (Offscreen) Also check: subtreeFlags & LayoutStatic
if (visibilityChanged && firstChild !== null) {
// We've just shown or hidden a Offscreen tree that contains layout effects.
// We only enter this code path for subtrees that are updated,
// because newly mounted ones would pass the LayoutMask check above.
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
continue;
}
}
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
fiber.tag === OffscreenComponent
这个条件 React 是一个开发中的API,预计会在某个 v18 发布。他的功能类似Vue中的keep-alive,用来在组件「失活」时在后台保存组件状态,暂时不管他。
function commitLayoutEffects_begin(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
...
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
if (enableSuspenseLayoutEffectSemantics && isModernRoot) {
const visibilityChanged =
!offscreenSubtreeIsHidden && offscreenSubtreeWasHidden;
if (visibilityChanged && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
continue;
}
}
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
commitLayoutEffects_begin 方法会从上往下
遍历 effectList ,最终会执行 commitLayoutMountEffects_complete 方法。
commitLayoutMountEffects_complete
function commitLayoutMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
const fiber = nextEffect;
if (
enableSuspenseLayoutEffectSemantics &&
isModernRoot &&
offscreenSubtreeWasHidden &&
!offscreenSubtreeIsHidden
) {
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
safelyCallCommitHookLayoutEffectListMount(fiber, fiber.return);
} finally {
recordLayoutEffectDuration(fiber);
}
} else {
safelyCallCommitHookLayoutEffectListMount(fiber, fiber.return);
}
break;
}
case ClassComponent: {
const instance = fiber.stateNode;
if (typeof instance.componentDidMount === 'function') {
safelyCallComponentDidMount(fiber, fiber.return, instance);
}
break;
}
}
// TODO (Offscreen) Check flags & RefStatic
switch (fiber.tag) {
case ClassComponent:
case HostComponent:
safelyAttachRef(fiber, fiber.return);
break;
}
} else if ((fiber.flags & LayoutMask) !== NoFlags) {
const current = fiber.alternate;
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(
null,
commitLayoutEffectOnFiber,
null,
root,
current,
fiber,
committedLanes,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
}
}
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
根据 nextEffect = fiber.return
这段代码可以看出 commitLayoutMountEffects_complete
会从下往上 遍历 effectList 。在该方法中会执行 commitLayoutEffectOnFiber 方法。
commitLayoutEffectOnFiber
commitLayoutEffectOnFiber 一共做了两件事:
- 调用生命周期钩子和 hook 相关操作
- commitAttachRef(赋值 ref)
commitLayoutEffectOnFiber 源码
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & (Update | Callback)) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
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 (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 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: {
// We have no life-cycles associated with text.
break;
}
case HostPortal: {
// We have no life-cycles associated with portals.
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:
break;
default:
invariant(
false,
'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 (enableScopeAPI) {
if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
commitAttachRef(finishedWork);
}
} else {
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
}
FunctionComponent
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & (Update | Callback)) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(HookLayout | HookHasEffect,finishedWork);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
commitHookEffectListMount(HookLayout | HookHasEffect,finishedWork);
}
break;
}
....
}
}
}
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
会执行 useLayoutEffect
的回调函数。
function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// Mount 执行 useLayoutEffect 回调函数
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
在 mutation 阶段会执行上一次 useLayoutEffect hook 的销毁函数,在 layout 阶段会调用 useLayoutEffect hook 本次更新的回调函数,是同步调用的。
ClassComponent
对于 ClassComponent 会根据 current 是否存在(current === null
) 来调用 instance.componentDidMount() 或者 instance.componentDidUpdate() 生命周期函数。
HostRoot
HostRoot,即 rootFiber,如果赋值了第三个参数回调函数,也会在此时调用。
ReactDOM.render(<App />, document.getElementById('root'),()=>{
console.log('ReactDOM.render 回调');
});
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;
}
}
// 执行 ReactDOM.render 第三个参数
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
commitUpdateQueue 函数
export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
): void {
// Commit the effects
const effects = finishedQueue.effects;
finishedQueue.effects = null;
if (effects !== null) {
for (let i = 0; i < effects.length; i++) {
const effect = effects[i];
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
}
}
commitAttachRef
if (enableScopeAPI) {
if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
commitAttachRef(finishedWork);
}
} else {
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
commitAttachRef
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
// 获取DOM实例
const instance = finishedWork.stateNode;
let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
// Moved outside to ensure DCE works with this flag
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
instanceToUse = instance;
}
if (typeof ref === 'function') {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
ref(instanceToUse);
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
ref(instanceToUse);
}
} else {
ref.current = instanceToUse;
}
}
}