跳到主要内容

认识

beforeMutation 执行DOM操作前

工作


before mutation阶段的代码很短,整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理,整个过程的工作如下:

  1. 处理DOM节点渲染/删除后的 autoFocusblur逻辑

  2. 调用getSnapshotBeforeUpdate生命周期钩子

  3. 调度useEffect

原理


commitBeforeMutationEffects

/react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function commitBeforeMutationEffects(firstChild: Fiber) {
let fiber = firstChild;
while (fiber !== null) {
if (fiber.deletions !== null) {
commitBeforeMutationEffectsDeletions(fiber.deletions);
}

if (fiber.child !== null) {
const primarySubtreeFlags = fiber.subtreeFlags & BeforeMutationMask;
if (primarySubtreeFlags !== NoFlags) {
commitBeforeMutationEffects(fiber.child);
}
}

if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(null, commitBeforeMutationEffectsImpl, null, fiber);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitBeforeMutationEffectsImpl(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
}
fiber = fiber.sibling;
}
}

理解

  1. 处理DOM节点渲染/删除后的 autoFocusblur 逻辑

  2. 调用getSnapshotBeforeUpdate生命周期钩子

  3. 调度useEffect

commitBeforeMutationEffectsDeletions

/react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function commitBeforeMutationEffectsDeletions(deletions: Array<Fiber>) {
for (let i = 0; i < deletions.length; i++) {
const fiber = deletions[i];

// TODO (effects) It would be nice to avoid calling doesFiberContain()
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
// Use it to store which part of the tree the focused instance is in?
// This assumes we can safely determine that instance during the "render" phase.

if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur();
}
}
}

commitBeforeMutationEffectsImpl

/react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
function commitBeforeMutationEffectsImpl(fiber: Fiber) {
const current = fiber.alternate;
const flags = fiber.flags;

if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
fiber.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, fiber) &&
doesFiberContain(fiber, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur();
}
}

if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(fiber);
commitBeforeMutationEffectOnFiber(current, fiber);
resetCurrentDebugFiberInDEV();
}

if ((flags & Passive) !== NoFlags) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
}

理解

  • commitBeforeMutationEffectOnFibercommitBeforeMutationLifeCycles的别名,在该方法内会调用getSnapshotBeforeUpdate。我们可以得知,getSnapshotBeforeUpdate是在commit阶段内的before mutation阶段调用的,由于commit阶段是同步的,所以不会遇到多次调用的问题

  • 调度Effect: scheduleCallback方法由Scheduler模块提供,用于以某个优先级异步调度一个回调函数。在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects。那么,useEffect如何被异步调度,以及为什么要异步(而不是同步)调度?

    • useEffect异步调用分为三步:

      • before mutation阶段在scheduleCallback中调度flushPassiveEffects

      • layout阶段之后将effectList赋值给rootWithPendingPassiveEffects

      • scheduleCallback触发flushPassiveEffectsflushPassiveEffects内部遍历rootWithPendingPassiveEffects

    • useEffect为什么要异步调用?

      useEffect 异步执行的原因主要是防止同步执行时阻塞浏览器渲染

commitBeforeMutationLifeCycles

/react/packages/react-reconciler/src/ReactFiberCommitWork.new.js
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
return;
}
case ClassComponent: {
if (finishedWork.flags & Snapshot) {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
if (__DEV__) {
const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
console.error(
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
'must be returned. You have returned undefined.',
getComponentName(finishedWork.type),
);
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}
case HostRoot: {
if (supportsMutation) {
if (finishedWork.flags & Snapshot) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
}
return;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
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.',
);
}