Effects list refactor continued: passive effects traversal (#19374)

* Adds new `Passive` subtree tag value.
* Adds recursive traversal for passive effects (mounts and unmounts).
* Removes `pendingPassiveHookEffectsMount` and `pendingPassiveHookEffectsUnmount` arrays from work loop.
* Re-adds sibling and child pointer detaching (temporarily removed in previous PR).
* Addresses some minor TODO comments left over from previous PRs.

---

Co-authored-by: Luna Ruan <luna@fb.com>
This commit is contained in:
Brian Vaughn
2020-07-29 16:54:22 -04:00
committed by GitHub
co-authored by Luna Ruan
parent 74cd7e5f17
commit eae90cdbe9
11 changed files with 463 additions and 279 deletions
@@ -15,7 +15,7 @@ import type {Fiber} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane';
import getComponentName from 'shared/getComponentName';
import {Placement, Deletion} from './ReactSideEffectTags';
import {Deletion, Placement} from './ReactSideEffectTags';
import {
getIteratorFn,
REACT_ELEMENT_TYPE,
@@ -29,7 +29,7 @@ import {
enableScopeAPI,
enableBlocksAPI,
} from 'shared/ReactFeatureFlags';
import {NoEffect, Placement} from './ReactSideEffectTags';
import {NoEffect, Placement, StaticMask} from './ReactSideEffectTags';
import {NoEffect as NoSubtreeEffect} from './ReactSubtreeTags';
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
import {
@@ -288,8 +288,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
workInProgress.type = current.type;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
workInProgress.subtreeTag = NoSubtreeEffect;
workInProgress.deletions = null;
@@ -308,6 +306,9 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
}
}
// Reset all effects except static ones.
// Static effects are not specific to a render.
workInProgress.effectTag = current.effectTag & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
@@ -2064,7 +2064,6 @@ function updateSuspensePrimaryChildren(
if (currentFallbackChildFragment !== null) {
// Delete the fallback child fragment
currentFallbackChildFragment.nextEffect = null;
currentFallbackChildFragment.effectTag = Deletion;
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
const deletions = workInProgress.deletions;
if (deletions === null) {
@@ -68,6 +68,7 @@ import {
Placement,
Snapshot,
Update,
Passive,
} from './ReactSideEffectTags';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
@@ -115,9 +116,8 @@ import {
captureCommitPhaseError,
resolveRetryWakeable,
markCommitTimeOfFallback,
enqueuePendingPassiveHookEffectMount,
enqueuePendingPassiveHookEffectUnmount,
enqueuePendingPassiveProfilerEffect,
schedulePassiveEffectCallback,
} from './ReactFiberWorkLoop.new';
import {
NoEffect as NoHookEffect,
@@ -130,6 +130,10 @@ import {
updateDeprecatedEventListeners,
unmountDeprecatedResponderListeners,
} from './ReactFiberDeprecatedEvents.new';
import {
NoEffect as NoSubtreeTag,
Passive as PassiveSubtreeTag,
} from './ReactSubtreeTags';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
@@ -381,26 +385,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(
finishedRoot: FiberRoot,
finishedWork: Fiber,
@@ -486,7 +470,9 @@ function commitLifeCycles(
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
schedulePassiveEffects(finishedWork);
if ((finishedWork.subtreeTag & PassiveSubtreeTag) !== NoSubtreeTag) {
schedulePassiveEffectCallback();
}
return;
}
case ClassComponent: {
@@ -892,7 +878,12 @@ function commitUnmount(
const {destroy, tag} = effect;
if (destroy !== undefined) {
if ((tag & HookPassive) !== NoHookEffect) {
enqueuePendingPassiveHookEffectUnmount(current, effect);
// TODO: Consider if we can move this block out of the synchronous commit phase
effect.tag |= HookHasEffect;
current.effectTag |= Passive;
schedulePassiveEffectCallback();
} else {
if (
enableProfilerTimer &&
@@ -1013,29 +1004,24 @@ function commitNestedUnmounts(
}
function detachFiberMutation(fiber: Fiber) {
// Cut off the return pointers to disconnect it from the tree. Ideally, we
// should clear the child pointer of the parent alternate to let this
// Cut off the return pointer to disconnect it from the tree.
// This enables us to detect and warn against state updates on an unmounted component.
// It also prevents events from bubbling from within disconnected components.
//
// Ideally, we should also clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child. This child
// itself will be GC:ed when the parent updates the next time.
// Note: we cannot null out sibling here, otherwise it can cause issues
// with findDOMNode and how it requires the sibling field to carry out
// traversal in a later effect. See PR #16820. We now clear the sibling
// field after effects, see: detachFiberAfterEffects.
fiber.alternate = null;
fiber.child = null;
fiber.dependencies = null;
fiber.firstEffect = null;
fiber.lastEffect = null;
fiber.memoizedProps = null;
fiber.memoizedState = null;
fiber.pendingProps = null;
fiber.return = null;
fiber.stateNode = null;
fiber.updateQueue = null;
if (__DEV__) {
fiber._debugOwner = null;
// one so we'll settle for GC:ing the subtree of this child.
// This child itself will be GC:ed when the parent updates the next time.
//
// Note that we can't clear child or sibling pointers yet.
// They're needed for passive effects and for findDOMNode.
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.return = null;
fiber.alternate = null;
}
fiber.return = null;
}
function emptyPortalContainer(current: Fiber) {
@@ -50,6 +50,7 @@ import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.ne
import {
Update as UpdateEffect,
Passive as PassiveEffect,
PassiveStatic as PassiveStaticEffect,
} from './ReactSideEffectTags';
import {
HasEffect as HookHasEffect,
@@ -1270,7 +1271,7 @@ function mountEffect(
}
}
return mountEffectImpl(
UpdateEffect | PassiveEffect,
UpdateEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
@@ -1631,7 +1632,8 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
const setId = mountState(id)[1];
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
currentlyRenderingFiber.effectTag |= UpdateEffect | PassiveEffect;
currentlyRenderingFiber.effectTag |=
UpdateEffect | PassiveEffect | PassiveStaticEffect;
pushEffect(
HookHasEffect | HookPassive,
() => {
@@ -124,7 +124,7 @@ function deleteHydratableInstance(
const childToDelete = createFiberFromHostInstanceForDeletion();
childToDelete.stateNode = instance;
childToDelete.return = returnFiber;
childToDelete.effectTag = Deletion;
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
@@ -16,6 +16,7 @@ import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {Effect as HookEffect} from './ReactFiberHooks.new';
import type {StackCursor} from './ReactFiberStack.new';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
import {
warnAboutDeprecatedLifecycles,
@@ -49,6 +50,11 @@ import {
flushSyncCallbackQueue,
scheduleSyncCallback,
} from './SchedulerWithReactIntegration.new';
import {
NoEffect as NoHookEffect,
HasEffect as HookHasEffect,
Passive as HookPassive,
} from './ReactHookEffectTags';
import {
logCommitStarted,
logCommitStopped,
@@ -126,7 +132,7 @@ import {
Snapshot,
Callback,
Passive,
PassiveUnmountPendingDev,
PassiveStatic,
Incomplete,
HostEffectMask,
Hydrating,
@@ -134,12 +140,14 @@ import {
BeforeMutationMask,
MutationMask,
LayoutMask,
PassiveMask,
} from './ReactSideEffectTags';
import {
NoEffect as NoSubtreeTag,
BeforeMutation,
Mutation,
Layout,
BeforeMutation as BeforeMutationSubtreeTag,
Mutation as MutationSubtreeTag,
Layout as LayoutSubtreeTag,
Passive as PassiveSubtreeTag,
} from './ReactSubtreeTags';
import {
NoLanePriority,
@@ -327,8 +335,6 @@ let rootDoesHavePassiveEffects: boolean = false;
let rootWithPendingPassiveEffects: FiberRoot | null = null;
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
let pendingPassiveEffectsLanes: Lanes = NoLanes;
let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
let pendingPassiveProfilerEffects: Array<Fiber> = [];
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
@@ -1882,13 +1888,16 @@ function resetChildLanes(completedWork: Fiber) {
const effectTag = child.effectTag;
if ((effectTag & BeforeMutationMask) !== NoEffect) {
subtreeTag |= BeforeMutation;
subtreeTag |= BeforeMutationSubtreeTag;
}
if ((effectTag & MutationMask) !== NoEffect) {
subtreeTag |= Mutation;
subtreeTag |= MutationSubtreeTag;
}
if ((effectTag & LayoutMask) !== NoEffect) {
subtreeTag |= Layout;
subtreeTag |= LayoutSubtreeTag;
}
if ((effectTag & PassiveMask) !== NoEffect) {
subtreeTag |= PassiveSubtreeTag;
}
// When a fiber is cloned, its actualDuration is reset to 0. This value will
@@ -1929,13 +1938,16 @@ function resetChildLanes(completedWork: Fiber) {
const effectTag = child.effectTag;
if ((effectTag & BeforeMutationMask) !== NoEffect) {
subtreeTag |= BeforeMutation;
subtreeTag |= BeforeMutationSubtreeTag;
}
if ((effectTag & MutationMask) !== NoEffect) {
subtreeTag |= Mutation;
subtreeTag |= MutationSubtreeTag;
}
if ((effectTag & LayoutMask) !== NoEffect) {
subtreeTag |= Layout;
subtreeTag |= LayoutSubtreeTag;
}
if ((effectTag & PassiveMask) !== NoEffect) {
subtreeTag |= PassiveSubtreeTag;
}
child = child.sibling;
@@ -2170,6 +2182,17 @@ function commitRootImpl(root, renderPriorityLevel) {
markLayoutEffectsStopped();
}
// If there are pending passive effects, schedule a callback to process them.
if ((finishedWork.subtreeTag & PassiveSubtreeTag) !== NoSubtreeTag) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
requestPaint();
@@ -2201,8 +2224,6 @@ function commitRootImpl(root, renderPriorityLevel) {
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// TODO (effects) Detach sibling pointers for deleted Fibers
}
// Read this again, since an effect might have updated it
@@ -2312,7 +2333,7 @@ function commitBeforeMutationEffects(firstChild: Fiber) {
}
if (fiber.child !== null) {
const primarySubtreeTag = fiber.subtreeTag & BeforeMutation;
const primarySubtreeTag = fiber.subtreeTag & BeforeMutationSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
commitBeforeMutationEffects(fiber.child);
}
@@ -2396,19 +2417,13 @@ function commitMutationEffects(
) {
let fiber = firstChild;
while (fiber !== null) {
if (fiber.deletions !== null) {
commitMutationEffectsDeletions(
fiber.deletions,
root,
renderPriorityLevel,
);
// TODO (effects) Don't clear this yet; we may need to cleanup passive effects
fiber.deletions = null;
const deletions = fiber.deletions;
if (deletions !== null) {
commitMutationEffectsDeletions(deletions, root, renderPriorityLevel);
}
if (fiber.child !== null) {
const primarySubtreeTag = fiber.subtreeTag & Mutation;
const primarySubtreeTag = fiber.subtreeTag & MutationSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
commitMutationEffects(fiber.child, root, renderPriorityLevel);
}
@@ -2538,7 +2553,16 @@ function commitMutationEffectsDeletions(
captureCommitPhaseError(childToDelete, error);
}
}
// Don't clear the Deletion effect yet; we also use it to know when we need to detach refs later.
}
}
export function schedulePassiveEffectCallback() {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
@@ -2550,7 +2574,7 @@ function commitLayoutEffects(
let fiber = firstChild;
while (fiber !== null) {
if (fiber.child !== null) {
const primarySubtreeTag = fiber.subtreeTag & Layout;
const primarySubtreeTag = fiber.subtreeTag & LayoutSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
commitLayoutEffects(fiber.child, root, committedLanes);
}
@@ -2645,46 +2669,253 @@ export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
}
}
export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber,
effect: HookEffect,
): void {
pendingPassiveHookEffectsMount.push(effect, fiber);
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
export function enqueuePendingPassiveHookEffectUnmount(
fiber: Fiber,
effect: HookEffect,
): void {
pendingPassiveHookEffectsUnmount.push(effect, fiber);
if (__DEV__) {
fiber.effectTag |= PassiveUnmountPendingDev;
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.effectTag |= PassiveUnmountPendingDev;
}
}
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
function invokePassiveEffectCreate(effect: HookEffect): void {
const create = effect.create;
effect.destroy = create();
}
function flushPassiveMountEffects(firstChild: Fiber): void {
let fiber = firstChild;
while (fiber !== null) {
const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag;
if (fiber.child !== null && primarySubtreeTag !== NoSubtreeTag) {
flushPassiveMountEffects(fiber.child);
}
if ((fiber.effectTag & Update) !== NoEffect) {
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
flushPassiveMountEffectsImpl(fiber);
}
}
}
fiber = fiber.sibling;
}
}
function flushPassiveMountEffectsImpl(fiber: Fiber): void {
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.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
) {
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
invokeGuardedCallback(
null,
invokePassiveEffectCreate,
null,
effect,
);
recordPassiveEffectDuration(fiber);
} else {
invokeGuardedCallback(
null,
invokePassiveEffectCreate,
null,
effect,
);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
const create = effect.create;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
effect.destroy = create();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
effect.destroy = create();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
}
effect = next;
} while (effect !== firstEffect);
}
}
function flushPassiveUnmountEffects(firstChild: Fiber): void {
let fiber = firstChild;
while (fiber !== null) {
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const fiberToDelete = deletions[i];
// If this fiber (or anything below it) has passive effects then traverse the subtree.
const primaryEffectTag = fiberToDelete.effectTag & PassiveMask;
const primarySubtreeTag = fiberToDelete.subtreeTag & PassiveSubtreeTag;
if (
primarySubtreeTag !== NoSubtreeTag ||
primaryEffectTag !== NoEffect
) {
flushPassiveUnmountEffectsInsideOfDeletedTree(fiberToDelete);
}
// Now that passive effects have been processed, it's safe to detach lingering pointers.
detachFiberAfterEffects(fiberToDelete);
}
}
const child = fiber.child;
if (child !== null) {
// If any children have passive effects then traverse the subtree.
// Note that this requires checking subtreeTag of the current Fiber,
// rather than the subtreeTag/effectsTag of the first child,
// since that would not cover passive effects in siblings.
const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
flushPassiveUnmountEffects(child);
}
}
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
const primaryEffectTag = fiber.effectTag & Passive;
if (primaryEffectTag !== NoEffect) {
flushPassiveUnmountEffectsImpl(fiber);
}
}
}
fiber = fiber.sibling;
}
}
function flushPassiveUnmountEffectsInsideOfDeletedTree(
firstChild: Fiber,
): void {
let fiber = firstChild;
while (fiber !== null) {
const child = fiber.child;
if (child !== null) {
// If any children have passive effects then traverse the subtree.
// Note that this requires checking subtreeTag of the current Fiber,
// rather than the subtreeTag/effectsTag of the first child,
// since that would not cover passive effects in siblings.
const primarySubtreeTag = fiber.subtreeTag & PassiveSubtreeTag;
if (primarySubtreeTag !== NoSubtreeTag) {
flushPassiveUnmountEffectsInsideOfDeletedTree(child);
}
}
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
const primaryEffectTag = fiber.effectTag & Passive;
if (primaryEffectTag !== NoEffect) {
flushPassiveUnmountEffectsImpl(fiber);
}
}
}
fiber = fiber.sibling;
}
}
function flushPassiveUnmountEffectsImpl(fiber: Fiber): void {
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.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
) {
const destroy = effect.destroy;
effect.destroy = undefined;
if (typeof destroy === 'function') {
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
invokeGuardedCallback(null, destroy, null);
recordPassiveEffectDuration(fiber);
} else {
invokeGuardedCallback(null, destroy, null);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
destroy();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
destroy();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
}
}
effect = next;
} while (effect !== firstEffect);
}
}
function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) {
return false;
@@ -2724,117 +2955,8 @@ function flushPassiveEffectsImpl() {
// e.g. a destroy function in one component may unintentionally override a ref
// value set by a create function in another component.
// Layout effects have the same constraint.
// First pass: Destroy stale passive effects.
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
for (let i = 0; i < unmountEffects.length; i += 2) {
const effect = ((unmountEffects[i]: any): HookEffect);
const fiber = ((unmountEffects[i + 1]: any): Fiber);
const destroy = effect.destroy;
effect.destroy = undefined;
if (__DEV__) {
fiber.effectTag &= ~PassiveUnmountPendingDev;
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.effectTag &= ~PassiveUnmountPendingDev;
}
}
if (typeof destroy === 'function') {
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
invokeGuardedCallback(null, destroy, null);
recordPassiveEffectDuration(fiber);
} else {
invokeGuardedCallback(null, destroy, null);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
destroy();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
destroy();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
}
}
// Second pass: Create new passive effects.
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = ((mountEffects[i]: any): HookEffect);
const fiber = ((mountEffects[i + 1]: any): Fiber);
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
startPassiveEffectTimer();
invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
recordPassiveEffectDuration(fiber);
} else {
invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
}
if (hasCaughtError()) {
invariant(fiber !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
const create = effect.create;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
effect.destroy = create();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
effect.destroy = create();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
}
// TODO (effects) Detach sibling pointers for deleted Fibers
flushPassiveUnmountEffects(root.current);
flushPassiveMountEffects(root.current);
if (enableProfilerTimer && enableProfilerCommitHooks) {
const profilerEffects = pendingPassiveProfilerEffects;
@@ -3230,10 +3352,24 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
return;
}
// If there are pending passive effects unmounts for this Fiber,
// we can assume that they would have prevented this update.
if ((fiber.effectTag & PassiveUnmountPendingDev) !== NoEffect) {
return;
if ((fiber.effectTag & PassiveStatic) !== NoEffect) {
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if (effect.destroy !== undefined) {
if ((effect.tag & HookPassive) !== NoHookEffect) {
return;
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
}
// We show the whole stack but dedupe on the top component's name because
@@ -3934,3 +4070,23 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
};
}
}
function detachFiberAfterEffects(fiber: Fiber): void {
// Null out fields to improve GC for references that may be lingering (e.g. DevTools).
// Note that we already cleared the return pointer in detachFiberMutation().
fiber.child = null;
fiber.deletions = null;
fiber.dependencies = null;
fiber.firstEffect = null;
fiber.lastEffect = null;
fiber.memoizedProps = null;
fiber.memoizedState = null;
fiber.pendingProps = null;
fiber.sibling = null;
fiber.stateNode = null;
fiber.updateQueue = null;
if (__DEV__) {
fiber._debugOwner = null;
}
}
+37 -23
View File
@@ -10,36 +10,50 @@
export type SideEffectTag = number;
// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b000000000000000;
export const PerformedWork = /* */ 0b000000000000001;
export const NoEffect = /* */ 0b0000000000000000;
export const PerformedWork = /* */ 0b0000000000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b000000000000010;
export const Update = /* */ 0b000000000000100;
export const PlacementAndUpdate = /* */ 0b000000000000110;
export const Deletion = /* */ 0b000000000001000;
export const ContentReset = /* */ 0b000000000010000;
export const Callback = /* */ 0b000000000100000;
export const DidCapture = /* */ 0b000000001000000;
export const Ref = /* */ 0b000000010000000;
export const Snapshot = /* */ 0b000000100000000;
export const Passive = /* */ 0b000001000000000;
export const PassiveUnmountPendingDev = /* */ 0b010000000000000;
export const Hydrating = /* */ 0b000010000000000;
export const HydratingAndUpdate = /* */ 0b000010000000100;
export const Placement = /* */ 0b0000000000000010;
export const Update = /* */ 0b0000000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000000110;
export const Deletion = /* */ 0b0000000000001000;
export const ContentReset = /* */ 0b0000000000010000;
export const Callback = /* */ 0b0000000000100000;
export const DidCapture = /* */ 0b0000000001000000;
export const Ref = /* */ 0b0000000010000000;
export const Snapshot = /* */ 0b0000000100000000;
export const Passive = /* */ 0b0000001000000000;
// TODO (effects) Remove this bit once the new reconciler is synced to the old.
export const PassiveUnmountPendingDev = /* */ 0b0010000000000000;
export const Hydrating = /* */ 0b0000010000000000;
export const HydratingAndUpdate = /* */ 0b0000010000000100;
// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b000001110100100;
export const LifecycleEffectMask = /* */ 0b0000001110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b000011111111111;
export const HostEffectMask = /* */ 0b0000011111111111;
// These are not really side effects, but we still reuse this field.
export const Incomplete = /* */ 0b000100000000000;
export const ShouldCapture = /* */ 0b001000000000000;
export const ForceUpdateForLegacySuspense = /* */ 0b100000000000000;
export const Incomplete = /* */ 0b0000100000000000;
export const ShouldCapture = /* */ 0b0001000000000000;
export const ForceUpdateForLegacySuspense = /* */ 0b0100000000000000;
// Static tags describe aspects of a fiber that are not specific to a render,
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
// This enables us to defer more work in the unmount case,
// since we can defer traversing the tree during layout to look for Passive effects,
// and instead rely on the static flag as a signal that there may be cleanup work.
export const PassiveStatic = /* */ 0b1000000000000000;
// Union of side effect groupings as pertains to subtreeTag
export const BeforeMutationMask = /* */ 0b000001100001010;
export const MutationMask = /* */ 0b000010010011110;
export const LayoutMask = /* */ 0b000000010100100;
export const BeforeMutationMask = /* */ 0b0000001100001010;
export const MutationMask = /* */ 0b0000010010011110;
export const LayoutMask = /* */ 0b0000000010100100;
export const PassiveMask = /* */ 0b1000001000001000;
// Union of tags that don't get reset on clones.
// This allows certain concepts to persist without recalculting them,
// e.g. whether a subtree contains passive effects or portals.
export const StaticMask = /* */ 0b1000000000000000;
+5 -4
View File
@@ -9,7 +9,8 @@
export type SubtreeTag = number;
export const NoEffect = /* */ 0b000;
export const BeforeMutation = /* */ 0b001;
export const Mutation = /* */ 0b010;
export const Layout = /* */ 0b100;
export const NoEffect = /* */ 0b0000;
export const BeforeMutation = /* */ 0b0001;
export const Mutation = /* */ 0b0010;
export const Layout = /* */ 0b0100;
export const Passive = /* */ 0b1000;
@@ -1454,7 +1454,7 @@ describe('ReactSuspense', () => {
]);
});
it('should call onInteractionScheduledWorkCompleted after suspending', done => {
it('should call onInteractionScheduledWorkCompleted after suspending', () => {
const subscriber = {
onInteractionScheduledWorkCompleted: jest.fn(),
onInteractionTraced: jest.fn(),
@@ -1512,13 +1512,11 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
expect(Scheduler).toFlushExpired([
expect(Scheduler).toFlushAndYield([
// Even though the promise for C was thrown three times, we should only
// re-render once.
'C',
]);
done();
});
expect(
@@ -486,24 +486,51 @@ describe('SchedulingProfiler', () => {
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
});
expect(marks.map(normalizeCodeLocInfo)).toEqual([
'--schedule-render-512',
'--render-start-512',
'--render-stop',
'--commit-start-512',
'--layout-effects-start-512',
'--layout-effects-stop',
'--commit-stop',
'--passive-effects-start-512',
toggleComponentStacks(
'--schedule-state-update-1024-Example-\n in Example (at **)',
),
'--passive-effects-stop',
'--render-start-1024',
'--render-stop',
'--commit-start-1024',
'--commit-stop',
]);
gate(({old}) => {
if (old) {
expect(marks.map(normalizeCodeLocInfo)).toEqual([
'--schedule-render-512',
'--render-start-512',
'--render-stop',
'--commit-start-512',
'--layout-effects-start-512',
'--layout-effects-stop',
'--commit-stop',
'--passive-effects-start-512',
toggleComponentStacks(
'--schedule-state-update-1024-Example-\n in Example (at **)',
),
'--passive-effects-stop',
'--render-start-1024',
'--render-stop',
'--commit-start-1024',
'--commit-stop',
]);
} else {
expect(marks.map(normalizeCodeLocInfo)).toEqual([
'--schedule-render-512',
'--render-start-512',
'--render-stop',
'--commit-start-512',
'--layout-effects-start-512',
'--layout-effects-stop',
'--commit-stop',
'--passive-effects-start-512',
toggleComponentStacks(
'--schedule-state-update-1024-Example-\n in Example (at **)',
),
'--passive-effects-stop',
'--render-start-1024',
'--render-stop',
'--commit-start-1024',
'--layout-effects-start-1024',
'--layout-effects-stop',
'--commit-stop',
'--passive-effects-start-1024',
'--passive-effects-stop',
]);
}
});
});
// @gate enableSchedulingProfiler