Land rest of effects refactor in main fork (#20644)

This commit is contained in:
Andrew Clark 2021-01-22 13:49:33 -06:00 committed by GitHub
parent a6b5256a29
commit 0fd6805c6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 234 additions and 392 deletions

View File

@ -116,7 +116,7 @@ module.exports = {
'react-internal/no-cross-fork-types': [ 'react-internal/no-cross-fork-types': [
ERROR, ERROR,
{ {
old: ['firstEffect', 'nextEffect'], old: [],
new: [], new: [],
}, },
], ],

View File

@ -13,12 +13,7 @@ import type {Fiber} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane.old'; import type {Lanes} from './ReactFiberLane.old';
import getComponentName from 'shared/getComponentName'; import getComponentName from 'shared/getComponentName';
import { import {Placement, ChildDeletion} from './ReactFiberFlags';
Deletion,
ChildDeletion,
Placement,
StaticMask,
} from './ReactFiberFlags';
import { import {
getIteratorFn, getIteratorFn,
REACT_ELEMENT_TYPE, REACT_ELEMENT_TYPE,
@ -268,21 +263,6 @@ function ChildReconciler(shouldTrackSideEffects) {
// Noop. // Noop.
return; return;
} }
// Deletions are added in reversed order so we add it to the front.
// At this point, the return fiber's effect list is empty except for
// deletions, so we can just append the deletion to the list. The remaining
// effects aren't added until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
const deletions = returnFiber.deletions; const deletions = returnFiber.deletions;
if (deletions === null) { if (deletions === null) {
returnFiber.deletions = [childToDelete]; returnFiber.deletions = [childToDelete];

View File

@ -144,10 +144,6 @@ function FiberNode(
// Effects // Effects
this.flags = NoFlags; this.flags = NoFlags;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.subtreeFlags = NoFlags; this.subtreeFlags = NoFlags;
this.deletions = null; this.deletions = null;
@ -285,10 +281,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
// Reset the effect tag. // Reset the effect tag.
workInProgress.flags = NoFlags; workInProgress.flags = NoFlags;
// The effect list is no longer valid. // The effects are no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
workInProgress.subtreeFlags = NoFlags; workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null; workInProgress.deletions = null;
@ -370,10 +363,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
// that child fiber is setting, not the reconciliation. // that child fiber is setting, not the reconciliation.
workInProgress.flags &= StaticMask | Placement; workInProgress.flags &= StaticMask | Placement;
// The effect list is no longer valid. // The effects are no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
const current = workInProgress.alternate; const current = workInProgress.alternate;
if (current === null) { if (current === null) {
@ -403,6 +393,9 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
workInProgress.lanes = current.lanes; workInProgress.lanes = current.lanes;
workInProgress.child = current.child; workInProgress.child = current.child;
// TODO: `subtreeFlags` should be reset to NoFlags, like we do in
// `createWorkInProgress`. Nothing reads this until the complete phase,
// currently, but it might in the future, and we should be consistent.
workInProgress.subtreeFlags = current.subtreeFlags; workInProgress.subtreeFlags = current.subtreeFlags;
workInProgress.deletions = null; workInProgress.deletions = null;
workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedProps = current.memoizedProps;
@ -847,9 +840,6 @@ export function assignFiberPropertiesInDEV(
target.dependencies = source.dependencies; target.dependencies = source.dependencies;
target.mode = source.mode; target.mode = source.mode;
target.flags = source.flags; target.flags = source.flags;
target.nextEffect = source.nextEffect;
target.firstEffect = source.firstEffect;
target.lastEffect = source.lastEffect;
target.subtreeFlags = source.subtreeFlags; target.subtreeFlags = source.subtreeFlags;
target.deletions = source.deletions; target.deletions = source.deletions;
target.lanes = source.lanes; target.lanes = source.lanes;

View File

@ -67,7 +67,6 @@ import {
DidCapture, DidCapture,
Update, Update,
Ref, Ref,
Deletion,
ChildDeletion, ChildDeletion,
ForceUpdateForLegacySuspense, ForceUpdateForLegacySuspense,
StaticMask, StaticMask,
@ -2199,10 +2198,6 @@ function updateSuspensePrimaryChildren(
primaryChildFragment.sibling = null; primaryChildFragment.sibling = null;
if (currentFallbackChildFragment !== null) { if (currentFallbackChildFragment !== null) {
// Delete the fallback child fragment // Delete the fallback child fragment
currentFallbackChildFragment.nextEffect = null;
currentFallbackChildFragment.flags =
(currentFallbackChildFragment.flags & StaticMask) | Deletion;
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
const deletions = workInProgress.deletions; const deletions = workInProgress.deletions;
if (deletions === null) { if (deletions === null) {
workInProgress.deletions = [currentFallbackChildFragment]; workInProgress.deletions = [currentFallbackChildFragment];
@ -2264,22 +2259,9 @@ function updateSuspenseFallbackChildren(
currentPrimaryChildFragment.treeBaseDuration; currentPrimaryChildFragment.treeBaseDuration;
} }
if (currentFallbackChildFragment !== null) { // The fallback fiber was added as a deletion during the first pass.
// The fallback fiber was added as a deletion effect during the first // However, since we're going to remain on the fallback, we no longer want
// pass. However, since we're going to remain on the fallback, we no // to delete it.
// longer want to delete it. So we need to remove it from the list.
// Deletions are stored on the same list as effects, and are always added
// to the front. So we know that the first effect must be the fallback
// deletion effect, and everything after that is from the primary free.
const firstPrimaryTreeEffect = currentFallbackChildFragment.nextEffect;
if (firstPrimaryTreeEffect !== null) {
workInProgress.firstEffect = firstPrimaryTreeEffect;
} else {
// TODO: Reset this somewhere else? Lol legacy mode is so weird.
workInProgress.firstEffect = workInProgress.lastEffect = null;
}
}
workInProgress.deletions = null; workInProgress.deletions = null;
} else { } else {
primaryChildFragment = createWorkInProgressOffscreenFiber( primaryChildFragment = createWorkInProgressOffscreenFiber(
@ -2776,7 +2758,6 @@ function initSuspenseListRenderState(
tail: null | Fiber, tail: null | Fiber,
lastContentRow: null | Fiber, lastContentRow: null | Fiber,
tailMode: SuspenseListTailMode, tailMode: SuspenseListTailMode,
lastEffectBeforeRendering: null | Fiber,
): void { ): void {
const renderState: null | SuspenseListRenderState = const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState; workInProgress.memoizedState;
@ -2788,7 +2769,6 @@ function initSuspenseListRenderState(
last: lastContentRow, last: lastContentRow,
tail: tail, tail: tail,
tailMode: tailMode, tailMode: tailMode,
lastEffect: lastEffectBeforeRendering,
}: SuspenseListRenderState); }: SuspenseListRenderState);
} else { } else {
// We can reuse the existing object from previous renders. // We can reuse the existing object from previous renders.
@ -2798,7 +2778,6 @@ function initSuspenseListRenderState(
renderState.last = lastContentRow; renderState.last = lastContentRow;
renderState.tail = tail; renderState.tail = tail;
renderState.tailMode = tailMode; renderState.tailMode = tailMode;
renderState.lastEffect = lastEffectBeforeRendering;
} }
} }
@ -2880,7 +2859,6 @@ function updateSuspenseListComponent(
tail, tail,
lastContentRow, lastContentRow,
tailMode, tailMode,
workInProgress.lastEffect,
); );
break; break;
} }
@ -2912,7 +2890,6 @@ function updateSuspenseListComponent(
tail, tail,
null, // last null, // last
tailMode, tailMode,
workInProgress.lastEffect,
); );
break; break;
} }
@ -2923,7 +2900,6 @@ function updateSuspenseListComponent(
null, // tail null, // tail
null, // last null, // last
undefined, undefined,
workInProgress.lastEffect,
); );
break; break;
} }
@ -3183,16 +3159,6 @@ function remountFiber(
// Delete the old fiber and place the new one. // Delete the old fiber and place the new one.
// Since the old fiber is disconnected, we have to schedule it manually. // Since the old fiber is disconnected, we have to schedule it manually.
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = current;
returnFiber.lastEffect = current;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = current;
}
current.nextEffect = null;
current.flags = (current.flags & StaticMask) | Deletion;
const deletions = returnFiber.deletions; const deletions = returnFiber.deletions;
if (deletions === null) { if (deletions === null) {
returnFiber.deletions = [current]; returnFiber.deletions = [current];

View File

@ -76,9 +76,10 @@ import {
Hydrating, Hydrating,
HydratingAndUpdate, HydratingAndUpdate,
Passive, Passive,
BeforeMutationMask,
MutationMask, MutationMask,
PassiveMask,
LayoutMask, LayoutMask,
PassiveMask,
PassiveUnmountPendingDev, PassiveUnmountPendingDev,
} from './ReactFiberFlags'; } from './ReactFiberFlags';
import getComponentName from 'shared/getComponentName'; import getComponentName from 'shared/getComponentName';
@ -129,13 +130,13 @@ import {
commitHydratedSuspenseInstance, commitHydratedSuspenseInstance,
clearContainer, clearContainer,
prepareScopeUpdate, prepareScopeUpdate,
prepareForCommit,
beforeActiveInstanceBlur,
} from './ReactFiberHostConfig'; } from './ReactFiberHostConfig';
import { import {
captureCommitPhaseError, captureCommitPhaseError,
resolveRetryWakeable, resolveRetryWakeable,
markCommitTimeOfFallback, markCommitTimeOfFallback,
enqueuePendingPassiveHookEffectMount,
enqueuePendingPassiveHookEffectUnmount,
enqueuePendingPassiveProfilerEffect, enqueuePendingPassiveProfilerEffect,
} from './ReactFiberWorkLoop.old'; } from './ReactFiberWorkLoop.old';
import { import {
@ -145,6 +146,7 @@ import {
Passive as HookPassive, Passive as HookPassive,
} from './ReactHookEffectTags'; } from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old'; import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
import {doesFiberContain} from './ReactFiberTreeReflection';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null; let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) { if (__DEV__) {
@ -260,18 +262,114 @@ function safelyCallDestroy(current: Fiber, destroy: () => void) {
} }
} }
function commitBeforeMutationLifeCycles( let focusedInstanceHandle: null | Fiber = null;
current: Fiber | null, let shouldFireAfterActiveInstanceBlur: boolean = false;
finishedWork: Fiber,
): void { export function commitBeforeMutationEffects(
switch (finishedWork.tag) { root: FiberRoot,
case FunctionComponent: firstChild: Fiber,
case ForwardRef: ) {
case SimpleMemoComponent: { focusedInstanceHandle = prepareForCommit(root.containerInfo);
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
// We no longer need to track the active instance fiber
const shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
// TODO: Should wrap this in flags check, too, as optimization
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();
}
}
}
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(
null,
commitBeforeMutationEffectsOnFiber,
null,
fiber,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return; return;
} }
case ClassComponent: {
if (finishedWork.flags & Snapshot) { nextEffect = fiber.return;
}
}
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.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 (
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) { if (current !== null) {
const prevProps = current.memoizedProps; const prevProps = current.memoizedProps;
const prevState = current.memoizedState; const prevState = current.memoizedState;
@ -325,30 +423,43 @@ function commitBeforeMutationLifeCycles(
} }
instance.__reactInternalSnapshotBeforeUpdate = snapshot; instance.__reactInternalSnapshotBeforeUpdate = snapshot;
} }
break;
} }
return; case HostRoot: {
} if (supportsMutation) {
case HostRoot: {
if (supportsMutation) {
if (finishedWork.flags & Snapshot) {
const root = finishedWork.stateNode; const root = finishedWork.stateNode;
clearContainer(root.containerInfo); 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.',
);
} }
return;
} }
case HostComponent:
case HostText: resetCurrentDebugFiberInDEV();
case HostPortal: }
case IncompleteClassComponent: }
// Nothing to do for these component types
return; function commitBeforeMutationEffectsDeletion(deletion: Fiber) {
// 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(deletion, ((focusedInstanceHandle: any): Fiber))) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(deletion);
} }
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.',
);
} }
function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) { function commitHookEffectListUnmount(flags: HookFlags, finishedWork: Fiber) {
@ -421,26 +532,6 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
} }
} }
function schedulePassiveEffects(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 {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
export function commitPassiveEffectDurations( export function commitPassiveEffectDurations(
finishedRoot: FiberRoot, finishedRoot: FiberRoot,
finishedWork: Fiber, finishedWork: Fiber,
@ -527,8 +618,6 @@ function commitLayoutEffectOnFiber(
} else { } else {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
} }
schedulePassiveEffects(finishedWork);
break; break;
} }
case ClassComponent: { case ClassComponent: {
@ -979,9 +1068,7 @@ function commitUnmount(
do { do {
const {destroy, tag} = effect; const {destroy, tag} = effect;
if (destroy !== undefined) { if (destroy !== undefined) {
if ((tag & HookPassive) !== NoHookEffect) { if ((tag & HookLayout) !== NoHookEffect) {
enqueuePendingPassiveHookEffectUnmount(current, effect);
} else {
if ( if (
enableProfilerTimer && enableProfilerTimer &&
enableProfilerCommitHooks && enableProfilerCommitHooks &&
@ -1127,9 +1214,6 @@ export function detachFiberAfterEffects(fiber: Fiber): void {
fiber.sibling = null; fiber.sibling = null;
fiber.stateNode = null; fiber.stateNode = null;
fiber.updateQueue = null; fiber.updateQueue = null;
fiber.nextEffect = null;
fiber.firstEffect = null;
fiber.lastEffect = null;
if (__DEV__) { if (__DEV__) {
fiber._debugOwner = null; fiber._debugOwner = null;
@ -2483,7 +2567,6 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
} }
export { export {
commitBeforeMutationLifeCycles,
commitResetTextContent, commitResetTextContent,
commitPlacement, commitPlacement,
commitDeletion, commitDeletion,

View File

@ -72,7 +72,9 @@ import {
NoFlags, NoFlags,
DidCapture, DidCapture,
Snapshot, Snapshot,
ChildDeletion,
StaticMask, StaticMask,
MutationMask,
} from './ReactFiberFlags'; } from './ReactFiberFlags';
import invariant from 'shared/invariant'; import invariant from 'shared/invariant';
@ -173,6 +175,31 @@ function markRef(workInProgress: Fiber) {
workInProgress.flags |= Ref; workInProgress.flags |= Ref;
} }
function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) {
const didBailout = current !== null && current.child === completedWork.child;
if (didBailout) {
return true;
}
if ((completedWork.flags & ChildDeletion) !== NoFlags) {
return false;
}
// TODO: If we move the `hadNoMutationsEffects` call after `bubbleProperties`
// then we only have to check the `completedWork.subtreeFlags`.
let child = completedWork.child;
while (child !== null) {
if (
(child.flags & MutationMask) !== NoFlags ||
(child.subtreeFlags & MutationMask) !== NoFlags
) {
return false;
}
child = child.sibling;
}
return true;
}
let appendAllChildren; let appendAllChildren;
let updateHostContainer; let updateHostContainer;
let updateHostComponent; let updateHostComponent;
@ -217,7 +244,7 @@ if (supportsMutation) {
} }
}; };
updateHostContainer = function(workInProgress: Fiber) { updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
// Noop // Noop
}; };
updateHostComponent = function( updateHostComponent = function(
@ -461,13 +488,13 @@ if (supportsMutation) {
node = node.sibling; node = node.sibling;
} }
}; };
updateHostContainer = function(workInProgress: Fiber) { updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
const portalOrRoot: { const portalOrRoot: {
containerInfo: Container, containerInfo: Container,
pendingChildren: ChildSet, pendingChildren: ChildSet,
... ...
} = workInProgress.stateNode; } = workInProgress.stateNode;
const childrenUnchanged = workInProgress.firstEffect === null; const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
if (childrenUnchanged) { if (childrenUnchanged) {
// No changes, just reuse the existing instance. // No changes, just reuse the existing instance.
} else { } else {
@ -492,7 +519,7 @@ if (supportsMutation) {
const oldProps = current.memoizedProps; const oldProps = current.memoizedProps;
// If there are no effects associated with this node, then none of our children had any updates. // If there are no effects associated with this node, then none of our children had any updates.
// This guarantees that we can reuse all of them. // This guarantees that we can reuse all of them.
const childrenUnchanged = workInProgress.firstEffect === null; const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
if (childrenUnchanged && oldProps === newProps) { if (childrenUnchanged && oldProps === newProps) {
// No changes, just reuse the existing instance. // No changes, just reuse the existing instance.
// Note that this might release a previous clone. // Note that this might release a previous clone.
@ -575,7 +602,7 @@ if (supportsMutation) {
}; };
} else { } else {
// No host operations // No host operations
updateHostContainer = function(workInProgress: Fiber) { updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
// Noop // Noop
}; };
updateHostComponent = function( updateHostComponent = function(
@ -847,7 +874,7 @@ function completeWork(
workInProgress.flags |= Snapshot; workInProgress.flags |= Snapshot;
} }
} }
updateHostContainer(workInProgress); updateHostContainer(current, workInProgress);
bubbleProperties(workInProgress); bubbleProperties(workInProgress);
return null; return null;
} }
@ -1142,7 +1169,7 @@ function completeWork(
} }
case HostPortal: case HostPortal:
popHostContainer(workInProgress); popHostContainer(workInProgress);
updateHostContainer(workInProgress); updateHostContainer(current, workInProgress);
if (current === null) { if (current === null) {
preparePortalMount(workInProgress.stateNode.containerInfo); preparePortalMount(workInProgress.stateNode.containerInfo);
} }
@ -1226,11 +1253,7 @@ function completeWork(
// Rerender the whole list, but this time, we'll force fallbacks // Rerender the whole list, but this time, we'll force fallbacks
// to stay in place. // to stay in place.
// Reset the effect list before doing the second pass since that's now invalid. // Reset the effect flags before doing the second pass since that's now invalid.
if (renderState.lastEffect === null) {
workInProgress.firstEffect = null;
}
workInProgress.lastEffect = renderState.lastEffect;
// Reset the child fibers to their original state. // Reset the child fibers to their original state.
workInProgress.subtreeFlags = NoFlags; workInProgress.subtreeFlags = NoFlags;
resetChildFibers(workInProgress, renderLanes); resetChildFibers(workInProgress, renderLanes);
@ -1301,15 +1324,6 @@ function completeWork(
!renderedTail.alternate && !renderedTail.alternate &&
!getIsHydrating() // We don't cut it if we're hydrating. !getIsHydrating() // We don't cut it if we're hydrating.
) { ) {
// We need to delete the row we just rendered.
// Reset the effect list to what it was before we rendered this
// child. The nested children have already appended themselves.
const lastEffect = (workInProgress.lastEffect =
renderState.lastEffect);
// Remove any effects that were appended after this point.
if (lastEffect !== null) {
lastEffect.nextEffect = null;
}
// We're done. // We're done.
bubbleProperties(workInProgress); bubbleProperties(workInProgress);
return null; return null;
@ -1369,7 +1383,6 @@ function completeWork(
const next = renderState.tail; const next = renderState.tail;
renderState.rendering = next; renderState.rendering = next;
renderState.tail = next.sibling; renderState.tail = next.sibling;
renderState.lastEffect = workInProgress.lastEffect;
renderState.renderingStartTime = now(); renderState.renderingStartTime = now();
next.sibling = null; next.sibling = null;

View File

@ -488,8 +488,8 @@ export function bailoutHooks(
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
) { ) {
workInProgress.flags &= ~( workInProgress.flags &= ~(
MountLayoutDevEffect |
MountPassiveDevEffect | MountPassiveDevEffect |
MountLayoutDevEffect |
PassiveEffect | PassiveEffect |
UpdateEffect UpdateEffect
); );
@ -1337,17 +1337,14 @@ function mountEffect(
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
) { ) {
return mountEffectImpl( return mountEffectImpl(
MountPassiveDevEffect | MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
UpdateEffect |
PassiveEffect |
PassiveStaticEffect,
HookPassive, HookPassive,
create, create,
deps, deps,
); );
} else { } else {
return mountEffectImpl( return mountEffectImpl(
UpdateEffect | PassiveEffect | PassiveStaticEffect, PassiveEffect | PassiveStaticEffect,
HookPassive, HookPassive,
create, create,
deps, deps,
@ -1365,12 +1362,7 @@ function updateEffect(
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber); warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
} }
} }
return updateEffectImpl( return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
} }
function mountLayoutEffect( function mountLayoutEffect(
@ -1749,10 +1741,9 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) { if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
if (__DEV__ && enableDoubleInvokingEffects) { if (__DEV__ && enableDoubleInvokingEffects) {
currentlyRenderingFiber.flags |= currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect;
MountPassiveDevEffect | UpdateEffect | PassiveEffect;
} else { } else {
currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect; currentlyRenderingFiber.flags |= PassiveEffect;
} }
pushEffect( pushEffect(
HookHasEffect | HookPassive, HookHasEffect | HookPassive,

View File

@ -24,13 +24,7 @@ import {
HostRoot, HostRoot,
SuspenseComponent, SuspenseComponent,
} from './ReactWorkTags'; } from './ReactWorkTags';
import { import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags';
Deletion,
ChildDeletion,
Placement,
Hydrating,
StaticMask,
} from './ReactFiberFlags';
import invariant from 'shared/invariant'; import invariant from 'shared/invariant';
import { import {
@ -130,19 +124,6 @@ function deleteHydratableInstance(
const childToDelete = createFiberFromHostInstanceForDeletion(); const childToDelete = createFiberFromHostInstanceForDeletion();
childToDelete.stateNode = instance; childToDelete.stateNode = instance;
childToDelete.return = returnFiber; childToDelete.return = returnFiber;
childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;
// This might seem like it belongs on progressedFirstDeletion. However,
// these children are not part of the reconciliation list of children.
// Even if we abort and rereconcile the children, that will try to hydrate
// again and the nodes are still in the host tree so these will be
// recreated.
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
const deletions = returnFiber.deletions; const deletions = returnFiber.deletions;
if (deletions === null) { if (deletions === null) {

View File

@ -60,9 +60,6 @@ export type SuspenseListRenderState = {|
tail: null | Fiber, tail: null | Fiber,
// Tail insertions setting. // Tail insertions setting.
tailMode: SuspenseListTailMode, tailMode: SuspenseListTailMode,
// Last Effect before we rendered the "rendering" item.
// Used to remove new effects added by the rendered item.
lastEffect: null | Fiber,
|}; |};
export function shouldCaptureSuspense( export function shouldCaptureSuspense(

View File

@ -188,8 +188,6 @@ function throwException(
) { ) {
// The source fiber did not complete. // The source fiber did not complete.
sourceFiber.flags |= Incomplete; sourceFiber.flags |= Incomplete;
// Its effect list is no longer valid.
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
if ( if (
value !== null && value !== null &&

View File

@ -13,7 +13,6 @@ import type {Lanes, Lane} from './ReactFiberLane.old';
import type {ReactPriorityLevel} from './ReactInternalTypes'; import type {ReactPriorityLevel} from './ReactInternalTypes';
import type {Interaction} from 'scheduler/src/Tracing'; import type {Interaction} from 'scheduler/src/Tracing';
import type {SuspenseState} from './ReactFiberSuspenseComponent.old'; import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
import type {Effect as HookEffect} from './ReactFiberHooks.old';
import type {StackCursor} from './ReactFiberStack.old'; import type {StackCursor} from './ReactFiberStack.old';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
import type {Flags} from './ReactFiberFlags'; import type {Flags} from './ReactFiberFlags';
@ -85,13 +84,11 @@ import * as Scheduler from 'scheduler';
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing'; import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
import { import {
prepareForCommit,
resetAfterCommit, resetAfterCommit,
scheduleTimeout, scheduleTimeout,
cancelTimeout, cancelTimeout,
noTimeout, noTimeout,
warnsIfNotActing, warnsIfNotActing,
beforeActiveInstanceBlur,
afterActiveInstanceBlur, afterActiveInstanceBlur,
clearContainer, clearContainer,
} from './ReactFiberHostConfig'; } from './ReactFiberHostConfig';
@ -122,17 +119,15 @@ import {
import {LegacyRoot} from './ReactRootTags'; import {LegacyRoot} from './ReactRootTags';
import { import {
NoFlags, NoFlags,
PerformedWork,
Placement, Placement,
Deletion,
ChildDeletion,
Snapshot,
Passive,
PassiveStatic, PassiveStatic,
Incomplete, Incomplete,
HostEffectMask, HostEffectMask,
Hydrating, Hydrating,
StaticMask, BeforeMutationMask,
MutationMask,
LayoutMask,
PassiveMask,
MountPassiveDev, MountPassiveDev,
MountLayoutDev, MountLayoutDev,
} from './ReactFiberFlags'; } from './ReactFiberFlags';
@ -185,14 +180,12 @@ import {
createClassErrorUpdate, createClassErrorUpdate,
} from './ReactFiberThrow.old'; } from './ReactFiberThrow.old';
import { import {
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber, commitBeforeMutationEffects,
commitLayoutEffects, commitLayoutEffects,
commitMutationEffects, commitMutationEffects,
commitPassiveEffectDurations, commitPassiveEffectDurations,
isSuspenseBoundaryBeingHidden,
commitPassiveMountEffects, commitPassiveMountEffects,
commitPassiveUnmountEffects, commitPassiveUnmountEffects,
detachFiberAfterEffects,
invokeLayoutEffectMountInDEV, invokeLayoutEffectMountInDEV,
invokePassiveEffectMountInDEV, invokePassiveEffectMountInDEV,
invokeLayoutEffectUnmountInDEV, invokeLayoutEffectUnmountInDEV,
@ -240,7 +233,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
// Used by `act` // Used by `act`
import enqueueTask from 'shared/enqueueTask'; import enqueueTask from 'shared/enqueueTask';
import {doesFiberContain} from './ReactFiberTreeReflection';
const ceil = Math.ceil; const ceil = Math.ceil;
@ -328,7 +320,6 @@ export function getRenderTargetTime(): number {
return workInProgressRootRenderTargetTime; return workInProgressRootRenderTargetTime;
} }
let nextEffect: Fiber | null = null;
let hasUncaughtError = false; let hasUncaughtError = false;
let firstUncaughtError = null; let firstUncaughtError = null;
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null; let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
@ -371,9 +362,6 @@ let currentEventPendingLanes: Lanes = NoLanes;
// We warn about state updates for unmounted components differently in this case. // We warn about state updates for unmounted components differently in this case.
let isFlushingPassiveEffects = false; let isFlushingPassiveEffects = false;
let focusedInstanceHandle: null | Fiber = null;
let shouldFireAfterActiveInstanceBlur: boolean = false;
export function getWorkInProgressRoot(): FiberRoot | null { export function getWorkInProgressRoot(): FiberRoot | null {
return workInProgressRoot; return workInProgressRoot;
} }
@ -1737,45 +1725,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
workInProgress = next; workInProgress = next;
return; return;
} }
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.flags & Incomplete) === NoFlags
) {
// 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 = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.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 flags = completedWork.flags;
// Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
if ((flags & ~StaticMask) > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else { } else {
// This fiber did not complete because something threw. Pop values off // This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary, // the stack without entering the complete phase. If this is a boundary,
@ -1812,8 +1761,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
} }
if (returnFiber !== null) { if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list. // Mark the parent fiber as incomplete and clear its subtree flags.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.flags |= Incomplete; returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags; returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null; returnFiber.deletions = null;
@ -1931,25 +1879,39 @@ function commitRootImpl(root, renderPriorityLevel) {
// times out. // times out.
} }
// Get the list of effects. // If there are pending passive effects, schedule a callback to process them.
let firstEffect; // Do this as early as possible, so it is queued before anything else that
if (finishedWork.flags > PerformedWork) { // might get scheduled in the commit phase. (See #16714.)
// A fiber's effect list consists only of its children, not itself. So if // TODO: Delete all other places that schedule the passive effect callback
// the root has an effect, we need to add it to the end of the list. The // They're redundant.
// resulting list is the set that would belong to the root's parent, if it if (
// had one; that is, all the effects in the tree including the root. (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
if (finishedWork.lastEffect !== null) { (finishedWork.flags & PassiveMask) !== NoFlags
finishedWork.lastEffect.nextEffect = finishedWork; ) {
firstEffect = finishedWork.firstEffect; if (!rootDoesHavePassiveEffects) {
} else { rootDoesHavePassiveEffects = true;
firstEffect = finishedWork; scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
} }
} else {
// There is no effect on the root.
firstEffect = finishedWork.firstEffect;
} }
if (firstEffect !== null) { // Check if there are any effects in the whole tree.
// TODO: This is left over from the effect list implementation, where we had
// to check for the existence of `firstEffect` to satsify Flow. I think the
// only other reason this optimization exists is because it affects profiling.
// Reconsider whether this is necessary.
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
let previousLanePriority; let previousLanePriority;
if (decoupleUpdatePriorityFromScheduler) { if (decoupleUpdatePriorityFromScheduler) {
previousLanePriority = getCurrentUpdateLanePriority(); previousLanePriority = getCurrentUpdateLanePriority();
@ -1970,32 +1932,10 @@ function commitRootImpl(root, renderPriorityLevel) {
// The first phase a "before mutation" phase. We use this phase to read the // The first phase a "before mutation" phase. We use this phase to read the
// state of the host tree right before we mutate it. This is where // state of the host tree right before we mutate it. This is where
// getSnapshotBeforeUpdate is called. // getSnapshotBeforeUpdate is called.
focusedInstanceHandle = prepareForCommit(root.containerInfo); const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
shouldFireAfterActiveInstanceBlur = false; root,
finishedWork,
nextEffect = firstEffect; );
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitBeforeMutationEffects();
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
// We no longer need to track the active instance fiber
focusedInstanceHandle = null;
if (enableProfilerTimer) { if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this // Mark the current commit time to be shared by all Profilers in this
@ -2082,31 +2022,6 @@ function commitRootImpl(root, renderPriorityLevel) {
rootWithPendingPassiveEffects = root; rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel; pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// We are done with the effect chain at this point so let's clear the
// nextEffect pointers to assist with GC. If we have passive effects, we'll
// clear this in flushPassiveEffects
// TODO: We should always do this in the passive phase, by scheduling
// a passive callback for every deletion.
nextEffect = firstEffect;
while (nextEffect !== null) {
const nextNextEffect = nextEffect.nextEffect;
nextEffect.nextEffect = null;
if (nextEffect.flags & ChildDeletion) {
const deletions = nextEffect.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
const alternate = deletion.alternate;
detachFiberAfterEffects(deletion);
if (alternate !== null) {
detachFiberAfterEffects(alternate);
}
}
}
}
nextEffect = nextNextEffect;
}
} }
// Read this again, since an effect might have updated it // Read this again, since an effect might have updated it
@ -2218,52 +2133,6 @@ function commitRootImpl(root, renderPriorityLevel) {
return null; return null;
} }
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const current = nextEffect.alternate;
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
if ((nextEffect.flags & Deletion) !== NoFlags) {
if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(nextEffect);
}
} else {
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
nextEffect.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, nextEffect) &&
doesFiberContain(nextEffect, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(nextEffect);
}
}
}
const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(nextEffect);
commitBeforeMutationEffectOnFiber(current, nextEffect);
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;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
export function flushPassiveEffects(): boolean { export function flushPassiveEffects(): boolean {
// Returns whether passive effects were flushed. // Returns whether passive effects were flushed.
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) { if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
@ -2302,32 +2171,6 @@ export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
} }
} }
export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber,
effect: HookEffect,
): void {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
export function enqueuePendingPassiveHookEffectUnmount(
fiber: Fiber,
effect: HookEffect,
): void {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
function flushPassiveEffectsImpl() { function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) { if (rootWithPendingPassiveEffects === null) {
return false; return false;