mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Reset new fork to old fork (#20254)
* Fix typo This typo was fixed in the new fork but not the old. * Reset new fork to old fork Something in the new fork is causing a topline metrics regression. We're not sure what it is, so we're going to split it into steps and bisect. As a first step, this resets the new fork back to the contents of the old fork. We will land this to confirm that the fork infra itself is not causing a regression. * Fix tests: Add `dfsEffectsRefactor` flag Some of the tests that gated on the effects refactor used the `new` flag. In order to bisect, we'll need to decompose the new fork changes into multiple steps. So I added a hardcoded test flag called `dfsEffectsRefactor` and set it to false. Will turn back on when we switch back to traversing the finished tree using DFS and `subtreeTag`.
This commit is contained in:
@@ -367,7 +367,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
// This is a new node.
|
||||
expect(span).not.toBe(span2);
|
||||
|
||||
if (gate(flags => flags.new)) {
|
||||
if (gate(flags => flags.dfsEffectsRefactor)) {
|
||||
// The effects list refactor causes this to be null because the Suspense Offscreen's child
|
||||
// is null. However, since we can't hydrate Suspense in legacy this change in behavior is ok
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
@@ -16,6 +16,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
// Don't feel too guilty if you have to delete this test.
|
||||
// @gate dfsEffectsRefactor
|
||||
// @gate new
|
||||
// @gate __DEV__
|
||||
test('warns in DEV if return pointer is inconsistent', async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {Fiber} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane';
|
||||
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import {Deletion, Placement} from './ReactFiberFlags';
|
||||
import {Placement, Deletion} from './ReactFiberFlags';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_ELEMENT_TYPE,
|
||||
@@ -256,13 +256,20 @@ function ChildReconciler(shouldTrackSideEffects) {
|
||||
// Noop.
|
||||
return;
|
||||
}
|
||||
const deletions = returnFiber.deletions;
|
||||
if (deletions === null) {
|
||||
returnFiber.deletions = [childToDelete];
|
||||
returnFiber.flags |= Deletion;
|
||||
// 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 {
|
||||
deletions.push(childToDelete);
|
||||
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
|
||||
}
|
||||
childToDelete.nextEffect = null;
|
||||
childToDelete.flags = Deletion;
|
||||
}
|
||||
|
||||
function deleteRemainingChildren(
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
enableFundamentalAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
|
||||
import {NoFlags, Placement} from './ReactFiberFlags';
|
||||
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
@@ -279,6 +279,13 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
|
||||
workInProgress.type = current.type;
|
||||
|
||||
// We already have an alternate.
|
||||
// Reset the effect tag.
|
||||
workInProgress.flags = NoFlags;
|
||||
|
||||
// The effect list is no longer valid.
|
||||
workInProgress.nextEffect = null;
|
||||
workInProgress.firstEffect = null;
|
||||
workInProgress.lastEffect = null;
|
||||
workInProgress.subtreeFlags = NoFlags;
|
||||
workInProgress.deletions = null;
|
||||
|
||||
@@ -292,9 +299,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all effects except static ones.
|
||||
// Static effects are not specific to a render.
|
||||
workInProgress.flags = current.flags & StaticMask;
|
||||
workInProgress.childLanes = current.childLanes;
|
||||
workInProgress.lanes = current.lanes;
|
||||
|
||||
@@ -360,6 +364,11 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
|
||||
// that child fiber is setting, not the reconciliation.
|
||||
workInProgress.flags &= Placement;
|
||||
|
||||
// The effect list is no longer valid.
|
||||
workInProgress.nextEffect = null;
|
||||
workInProgress.firstEffect = null;
|
||||
workInProgress.lastEffect = null;
|
||||
|
||||
const current = workInProgress.alternate;
|
||||
if (current === null) {
|
||||
// Reset to createFiber's initial values.
|
||||
|
||||
@@ -58,10 +58,10 @@ import {
|
||||
Hydrating,
|
||||
ContentReset,
|
||||
DidCapture,
|
||||
Update,
|
||||
Ref,
|
||||
Deletion,
|
||||
ForceUpdateForLegacySuspense,
|
||||
StaticMask,
|
||||
} from './ReactFiberFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
@@ -671,6 +671,8 @@ function updateProfiler(
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
if (enableProfilerTimer) {
|
||||
workInProgress.flags |= Update;
|
||||
|
||||
// Reset effect durations for the next eventual effect phase.
|
||||
// These are reset during render to allow the DevTools commit hook a chance to read them,
|
||||
const stateNode = workInProgress.stateNode;
|
||||
@@ -1077,9 +1079,6 @@ function updateHostComponent(
|
||||
workInProgress.flags |= ContentReset;
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.flags |= PerformedWork;
|
||||
|
||||
markRef(current, workInProgress);
|
||||
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
|
||||
return workInProgress.child;
|
||||
@@ -2005,14 +2004,9 @@ function updateSuspensePrimaryChildren(
|
||||
primaryChildFragment.sibling = null;
|
||||
if (currentFallbackChildFragment !== null) {
|
||||
// Delete the fallback child fragment
|
||||
const deletions = workInProgress.deletions;
|
||||
if (deletions === null) {
|
||||
workInProgress.deletions = [currentFallbackChildFragment];
|
||||
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
|
||||
workInProgress.flags |= Deletion;
|
||||
} else {
|
||||
deletions.push(currentFallbackChildFragment);
|
||||
}
|
||||
currentFallbackChildFragment.nextEffect = null;
|
||||
currentFallbackChildFragment.flags = Deletion;
|
||||
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
|
||||
}
|
||||
|
||||
workInProgress.child = primaryChildFragment;
|
||||
@@ -2069,19 +2063,24 @@ function updateSuspenseFallbackChildren(
|
||||
|
||||
// The fallback fiber was added as a deletion effect during the first pass.
|
||||
// However, since we're going to remain on the fallback, we no longer want
|
||||
// to delete it.
|
||||
workInProgress.deletions = null;
|
||||
// to delete it. So we need to remove it from the list. Deletions are stored
|
||||
// on the same list as effects. We want to keep the effects from the primary
|
||||
// tree. So we copy the primary child fragment's effect list, which does not
|
||||
// include the fallback deletion effect.
|
||||
const progressedLastEffect = primaryChildFragment.lastEffect;
|
||||
if (progressedLastEffect !== null) {
|
||||
workInProgress.firstEffect = primaryChildFragment.firstEffect;
|
||||
workInProgress.lastEffect = progressedLastEffect;
|
||||
progressedLastEffect.nextEffect = null;
|
||||
} else {
|
||||
// TODO: Reset this somewhere else? Lol legacy mode is so weird.
|
||||
workInProgress.firstEffect = workInProgress.lastEffect = null;
|
||||
}
|
||||
} else {
|
||||
primaryChildFragment = createWorkInProgressOffscreenFiber(
|
||||
currentPrimaryChildFragment,
|
||||
primaryChildProps,
|
||||
);
|
||||
|
||||
// Since we're reusing a current tree, we need to reuse the flags, too.
|
||||
// (We don't do this in legacy mode, because in legacy mode we don't re-use
|
||||
// the current tree; see previous branch.)
|
||||
primaryChildFragment.subtreeFlags =
|
||||
currentPrimaryChildFragment.subtreeFlags & StaticMask;
|
||||
}
|
||||
let fallbackChildFragment;
|
||||
if (currentFallbackChildFragment !== null) {
|
||||
@@ -2566,6 +2565,7 @@ function initSuspenseListRenderState(
|
||||
tail: null | Fiber,
|
||||
lastContentRow: null | Fiber,
|
||||
tailMode: SuspenseListTailMode,
|
||||
lastEffectBeforeRendering: null | Fiber,
|
||||
): void {
|
||||
const renderState: null | SuspenseListRenderState =
|
||||
workInProgress.memoizedState;
|
||||
@@ -2577,6 +2577,7 @@ function initSuspenseListRenderState(
|
||||
last: lastContentRow,
|
||||
tail: tail,
|
||||
tailMode: tailMode,
|
||||
lastEffect: lastEffectBeforeRendering,
|
||||
}: SuspenseListRenderState);
|
||||
} else {
|
||||
// We can reuse the existing object from previous renders.
|
||||
@@ -2586,6 +2587,7 @@ function initSuspenseListRenderState(
|
||||
renderState.last = lastContentRow;
|
||||
renderState.tail = tail;
|
||||
renderState.tailMode = tailMode;
|
||||
renderState.lastEffect = lastEffectBeforeRendering;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2667,6 +2669,7 @@ function updateSuspenseListComponent(
|
||||
tail,
|
||||
lastContentRow,
|
||||
tailMode,
|
||||
workInProgress.lastEffect,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -2698,6 +2701,7 @@ function updateSuspenseListComponent(
|
||||
tail,
|
||||
null, // last
|
||||
tailMode,
|
||||
workInProgress.lastEffect,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -2708,6 +2712,7 @@ function updateSuspenseListComponent(
|
||||
null, // tail
|
||||
null, // last
|
||||
undefined,
|
||||
workInProgress.lastEffect,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -2967,14 +2972,15 @@ function remountFiber(
|
||||
|
||||
// Delete the old fiber and place the new one.
|
||||
// Since the old fiber is disconnected, we have to schedule it manually.
|
||||
const deletions = returnFiber.deletions;
|
||||
if (deletions === null) {
|
||||
returnFiber.deletions = [current];
|
||||
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
|
||||
returnFiber.flags |= Deletion;
|
||||
const last = returnFiber.lastEffect;
|
||||
if (last !== null) {
|
||||
last.nextEffect = current;
|
||||
returnFiber.lastEffect = current;
|
||||
} else {
|
||||
deletions.push(current);
|
||||
returnFiber.firstEffect = returnFiber.lastEffect = current;
|
||||
}
|
||||
current.nextEffect = null;
|
||||
current.flags = Deletion;
|
||||
|
||||
newWorkInProgress.flags |= Placement;
|
||||
|
||||
@@ -3059,6 +3065,15 @@ function beginWork(
|
||||
}
|
||||
case Profiler:
|
||||
if (enableProfilerTimer) {
|
||||
// Profiler should only call onRender when one of its descendants actually rendered.
|
||||
const hasChildWork = includesSomeLane(
|
||||
renderLanes,
|
||||
workInProgress.childLanes,
|
||||
);
|
||||
if (hasChildWork) {
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
|
||||
// Reset effect durations for the next eventual effect phase.
|
||||
// These are reset during render to allow the DevTools commit hook a chance to read them,
|
||||
const stateNode = workInProgress.stateNode;
|
||||
@@ -3165,6 +3180,7 @@ function beginWork(
|
||||
// update in the past but didn't complete it.
|
||||
renderState.rendering = null;
|
||||
renderState.tail = null;
|
||||
renderState.lastEffect = null;
|
||||
}
|
||||
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
|
||||
|
||||
|
||||
@@ -12,14 +12,13 @@ import type {Lanes} from './ReactFiberLane';
|
||||
import type {UpdateQueue} from './ReactUpdateQueue.new';
|
||||
|
||||
import * as React from 'react';
|
||||
import {Update, Snapshot, MountLayoutDev} from './ReactFiberFlags';
|
||||
import {Update, Snapshot} from './ReactFiberFlags';
|
||||
import {
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
disableLegacyContext,
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
warnAboutDeprecatedLifecycles,
|
||||
enableDoubleInvokingEffects,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
|
||||
import {isMounted} from './ReactFiberTreeReflection';
|
||||
@@ -30,13 +29,7 @@ import invariant from 'shared/invariant';
|
||||
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
|
||||
import {
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
NoMode,
|
||||
StrictMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
|
||||
|
||||
import {
|
||||
enqueueUpdate,
|
||||
@@ -897,16 +890,7 @@ function mountClassInstance(
|
||||
}
|
||||
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
// Never double-invoke effects for legacy roots.
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,15 +960,7 @@ function resumeMountClassInstance(
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1027,29 +1003,13 @@ function resumeMountClassInstance(
|
||||
}
|
||||
}
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
} else {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
|
||||
// If shouldComponentUpdate returned false, we should still update the
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {Lanes, Lane} from './ReactFiberLane';
|
||||
import type {Lanes} from './ReactFiberLane';
|
||||
import type {
|
||||
ReactFundamentalComponentInstance,
|
||||
ReactScopeInstance,
|
||||
@@ -57,28 +57,8 @@ import {
|
||||
OffscreenComponent,
|
||||
LegacyHiddenComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
ProfileMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
Ref,
|
||||
Update,
|
||||
Callback,
|
||||
Passive,
|
||||
Deletion,
|
||||
NoFlags,
|
||||
DidCapture,
|
||||
Snapshot,
|
||||
Visibility,
|
||||
MutationMask,
|
||||
LayoutMask,
|
||||
PassiveMask,
|
||||
StaticMask,
|
||||
PerformedWork,
|
||||
} from './ReactFiberFlags';
|
||||
import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
@@ -148,16 +128,9 @@ import {
|
||||
renderHasNotSuspendedYet,
|
||||
popRenderLanes,
|
||||
getRenderTargetTime,
|
||||
subtreeRenderLanes,
|
||||
} from './ReactFiberWorkLoop.new';
|
||||
import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
|
||||
import {
|
||||
OffscreenLane,
|
||||
SomeRetryLane,
|
||||
NoLanes,
|
||||
includesSomeLane,
|
||||
mergeLanes,
|
||||
} from './ReactFiberLane';
|
||||
import {OffscreenLane, SomeRetryLane} from './ReactFiberLane';
|
||||
import {resetChildFibers} from './ReactChildFiber.new';
|
||||
import {createScopeInstance} from './ReactFiberScope.new';
|
||||
import {transferActualDuration} from './ReactProfilerTimer.new';
|
||||
@@ -172,31 +145,6 @@ function markRef(workInProgress: Fiber) {
|
||||
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 & Deletion) !== 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 | Deletion)) !== NoFlags) {
|
||||
return false;
|
||||
}
|
||||
if ((child.subtreeFlags & (MutationMask | Deletion)) !== NoFlags) {
|
||||
return false;
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let appendAllChildren;
|
||||
let updateHostContainer;
|
||||
let updateHostComponent;
|
||||
@@ -241,7 +189,7 @@ if (supportsMutation) {
|
||||
}
|
||||
};
|
||||
|
||||
updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
|
||||
updateHostContainer = function(workInProgress: Fiber) {
|
||||
// Noop
|
||||
};
|
||||
updateHostComponent = function(
|
||||
@@ -485,13 +433,13 @@ if (supportsMutation) {
|
||||
node = node.sibling;
|
||||
}
|
||||
};
|
||||
updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
|
||||
updateHostContainer = function(workInProgress: Fiber) {
|
||||
const portalOrRoot: {
|
||||
containerInfo: Container,
|
||||
pendingChildren: ChildSet,
|
||||
...
|
||||
} = workInProgress.stateNode;
|
||||
const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
|
||||
const childrenUnchanged = workInProgress.firstEffect === null;
|
||||
if (childrenUnchanged) {
|
||||
// No changes, just reuse the existing instance.
|
||||
} else {
|
||||
@@ -516,7 +464,7 @@ if (supportsMutation) {
|
||||
const oldProps = current.memoizedProps;
|
||||
// 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.
|
||||
const childrenUnchanged = hadNoMutationsEffects(current, workInProgress);
|
||||
const childrenUnchanged = workInProgress.firstEffect === null;
|
||||
if (childrenUnchanged && oldProps === newProps) {
|
||||
// No changes, just reuse the existing instance.
|
||||
// Note that this might release a previous clone.
|
||||
@@ -599,7 +547,7 @@ if (supportsMutation) {
|
||||
};
|
||||
} else {
|
||||
// No host operations
|
||||
updateHostContainer = function(current: null | Fiber, workInProgress: Fiber) {
|
||||
updateHostContainer = function(workInProgress: Fiber) {
|
||||
// Noop
|
||||
};
|
||||
updateHostComponent = function(
|
||||
@@ -692,126 +640,6 @@ function cutOffTailIfNeeded(
|
||||
}
|
||||
}
|
||||
|
||||
function bubbleProperties(completedWork: Fiber) {
|
||||
const didBailout =
|
||||
completedWork.alternate !== null &&
|
||||
completedWork.alternate.child === completedWork.child;
|
||||
|
||||
let newChildLanes = NoLanes;
|
||||
let subtreeFlags = NoFlags;
|
||||
|
||||
if (!didBailout) {
|
||||
// Bubble up the earliest expiration time.
|
||||
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
|
||||
// In profiling mode, resetChildExpirationTime is also used to reset
|
||||
// profiler durations.
|
||||
let actualDuration = completedWork.actualDuration;
|
||||
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
|
||||
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
|
||||
subtreeFlags |= child.subtreeFlags;
|
||||
subtreeFlags |= child.flags;
|
||||
|
||||
// When a fiber is cloned, its actualDuration is reset to 0. This value will
|
||||
// only be updated if work is done on the fiber (i.e. it doesn't bailout).
|
||||
// When work is done, it should bubble to the parent's actualDuration. If
|
||||
// the fiber has not been cloned though, (meaning no work was done), then
|
||||
// this value will reflect the amount of time spent working on a previous
|
||||
// render. In that case it should not bubble. We determine whether it was
|
||||
// cloned by comparing the child pointer.
|
||||
actualDuration += child.actualDuration;
|
||||
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
completedWork.actualDuration = actualDuration;
|
||||
completedWork.treeBaseDuration = treeBaseDuration;
|
||||
} else {
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
|
||||
subtreeFlags |= child.subtreeFlags;
|
||||
subtreeFlags |= child.flags;
|
||||
|
||||
// Update the return pointer so the tree is consistent. This is a code
|
||||
// smell because it assumes the commit phase is never concurrent with
|
||||
// the render phase. Will address during refactor to alternate model.
|
||||
child.return = completedWork;
|
||||
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
completedWork.subtreeFlags |= subtreeFlags;
|
||||
} else {
|
||||
// Bubble up the earliest expiration time.
|
||||
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
|
||||
// In profiling mode, resetChildExpirationTime is also used to reset
|
||||
// profiler durations.
|
||||
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
|
||||
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
|
||||
// "Static" flags share the lifetime of the fiber/hook they belong to,
|
||||
// so we should bubble those up even during a bailout. All the other
|
||||
// flags have a lifetime only of a single render + commit, so we should
|
||||
// ignore them.
|
||||
subtreeFlags |= child.subtreeFlags & StaticMask;
|
||||
subtreeFlags |= child.flags & StaticMask;
|
||||
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
completedWork.treeBaseDuration = treeBaseDuration;
|
||||
} else {
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
|
||||
// "Static" flags share the lifetime of the fiber/hook they belong to,
|
||||
// so we should bubble those up even during a bailout. All the other
|
||||
// flags have a lifetime only of a single render + commit, so we should
|
||||
// ignore them.
|
||||
subtreeFlags |= child.subtreeFlags & StaticMask;
|
||||
subtreeFlags |= child.flags & StaticMask;
|
||||
|
||||
// Update the return pointer so the tree is consistent. This is a code
|
||||
// smell because it assumes the commit phase is never concurrent with
|
||||
// the render phase. Will address during refactor to alternate model.
|
||||
child.return = completedWork;
|
||||
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
completedWork.subtreeFlags |= subtreeFlags;
|
||||
}
|
||||
|
||||
completedWork.childLanes = newChildLanes;
|
||||
|
||||
return didBailout;
|
||||
}
|
||||
|
||||
function completeWork(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
@@ -827,16 +655,15 @@ function completeWork(
|
||||
case ForwardRef:
|
||||
case Fragment:
|
||||
case Mode:
|
||||
case Profiler:
|
||||
case ContextConsumer:
|
||||
case MemoComponent:
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
case ClassComponent: {
|
||||
const Component = workInProgress.type;
|
||||
if (isLegacyContextProvider(Component)) {
|
||||
popLegacyContext(workInProgress);
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case HostRoot: {
|
||||
@@ -864,8 +691,7 @@ function completeWork(
|
||||
workInProgress.flags |= Snapshot;
|
||||
}
|
||||
}
|
||||
updateHostContainer(current, workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
updateHostContainer(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case HostComponent: {
|
||||
@@ -892,7 +718,6 @@ function completeWork(
|
||||
'caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
// This can happen when we abort work.
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -950,7 +775,6 @@ function completeWork(
|
||||
markRef(workInProgress);
|
||||
}
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case HostText: {
|
||||
@@ -985,58 +809,6 @@ function completeWork(
|
||||
);
|
||||
}
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case Profiler: {
|
||||
const didBailout = bubbleProperties(workInProgress);
|
||||
if (!didBailout) {
|
||||
// Use subtreeFlags to determine which commit callbacks should fire.
|
||||
// TODO: Move this logic to the commit phase, since we already check if
|
||||
// a fiber's subtree contains effects. Refactor the commit phase's
|
||||
// depth-first traversal so that we can put work tag-specific logic
|
||||
// before or after committing a subtree's effects.
|
||||
const OnRenderFlag = Update;
|
||||
const OnCommitFlag = Callback;
|
||||
const OnPostCommitFlag = Passive;
|
||||
const subtreeFlags = workInProgress.subtreeFlags;
|
||||
const flags = workInProgress.flags;
|
||||
let newFlags = flags;
|
||||
|
||||
// Call onRender any time this fiber or its subtree are worked on.
|
||||
if (
|
||||
(flags & PerformedWork) !== NoFlags ||
|
||||
(subtreeFlags & PerformedWork) !== NoFlags
|
||||
) {
|
||||
newFlags |= OnRenderFlag;
|
||||
}
|
||||
|
||||
// Call onCommit only if the subtree contains layout work, or if it
|
||||
// contains deletions, since those might result in unmount work, which
|
||||
// we include in the same measure.
|
||||
// TODO: Can optimize by using a static flag to track whether a tree
|
||||
// contains layout effects, like we do for passive effects.
|
||||
if (
|
||||
(flags & (LayoutMask | Deletion)) !== NoFlags ||
|
||||
(subtreeFlags & (LayoutMask | Deletion)) !== NoFlags
|
||||
) {
|
||||
newFlags |= OnCommitFlag;
|
||||
}
|
||||
|
||||
// Call onPostCommit only if the subtree contains passive work.
|
||||
// Don't have to check for deletions, because Deletion is already
|
||||
// a passive flag.
|
||||
if (
|
||||
(flags & PassiveMask) !== NoFlags ||
|
||||
(subtreeFlags & PassiveMask) !== NoFlags
|
||||
) {
|
||||
newFlags |= OnPostCommitFlag;
|
||||
}
|
||||
workInProgress.flags = newFlags;
|
||||
} else {
|
||||
// This fiber and its subtree bailed out, so don't fire any callbacks.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
@@ -1056,20 +828,6 @@ function completeWork(
|
||||
if (enableSchedulerTracing) {
|
||||
markSpawnedWork(OffscreenLane);
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doens't support type casting in combiation with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// We should never have been in a hydration state if we didn't have a current.
|
||||
@@ -1086,20 +844,6 @@ function completeWork(
|
||||
// If something suspended, schedule an effect to attach retry listeners.
|
||||
// So we might as well always mark this.
|
||||
workInProgress.flags |= Update;
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doens't support type casting in combiation with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1115,7 +859,6 @@ function completeWork(
|
||||
) {
|
||||
transferActualDuration(workInProgress);
|
||||
}
|
||||
// Don't bubble properties in this case.
|
||||
return workInProgress;
|
||||
}
|
||||
|
||||
@@ -1169,8 +912,8 @@ function completeWork(
|
||||
// TODO: Only schedule updates if not prevDidTimeout.
|
||||
if (nextDidTimeout) {
|
||||
// If this boundary just timed out, schedule an effect to attach a
|
||||
// retry listener to the promise.
|
||||
// TODO: Move to passive phase
|
||||
// retry listener to the promise. This flag is also used to hide the
|
||||
// primary children.
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
}
|
||||
@@ -1182,7 +925,7 @@ function completeWork(
|
||||
// primary children. In mutation mode, we also need the flag to
|
||||
// *unhide* children that were previously hidden, so check if this
|
||||
// is currently timed out, too.
|
||||
workInProgress.flags |= Update | Visibility;
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -1191,36 +934,20 @@ function completeWork(
|
||||
workInProgress.memoizedProps.suspenseCallback != null
|
||||
) {
|
||||
// Always notify the callback
|
||||
// TODO: Move to passive phase
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
if (nextDidTimeout) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doens't support type casting in combiation with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case HostPortal:
|
||||
popHostContainer(workInProgress);
|
||||
updateHostContainer(current, workInProgress);
|
||||
updateHostContainer(workInProgress);
|
||||
if (current === null) {
|
||||
preparePortalMount(workInProgress.stateNode.containerInfo);
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
case ContextProvider:
|
||||
// Pop provider fiber
|
||||
popProvider(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
case IncompleteClassComponent: {
|
||||
// Same as class component case. I put it down here so that the tags are
|
||||
@@ -1229,7 +956,6 @@ function completeWork(
|
||||
if (isLegacyContextProvider(Component)) {
|
||||
popLegacyContext(workInProgress);
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case SuspenseListComponent: {
|
||||
@@ -1241,7 +967,6 @@ function completeWork(
|
||||
if (renderState === null) {
|
||||
// We're running in the default, "independent" mode.
|
||||
// We don't do anything in this mode.
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1294,8 +1019,12 @@ function completeWork(
|
||||
|
||||
// Rerender the whole list, but this time, we'll force fallbacks
|
||||
// to stay in place.
|
||||
// Reset the effect list 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.
|
||||
workInProgress.subtreeFlags = NoFlags;
|
||||
resetChildFibers(workInProgress, renderLanes);
|
||||
|
||||
// Set up the Suspense Context to force suspense and immediately
|
||||
@@ -1307,7 +1036,6 @@ function completeWork(
|
||||
ForceSuspenseFallback,
|
||||
),
|
||||
);
|
||||
// Don't bubble properties in this case.
|
||||
return workInProgress.child;
|
||||
}
|
||||
row = row.sibling;
|
||||
@@ -1364,8 +1092,16 @@ function completeWork(
|
||||
!renderedTail.alternate &&
|
||||
!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.
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
} else if (
|
||||
@@ -1385,10 +1121,13 @@ function completeWork(
|
||||
cutOffTailIfNeeded(renderState, false);
|
||||
|
||||
// Since nothing actually suspended, there will nothing to ping this
|
||||
// to get it started back up to attempt the next item. If we can show
|
||||
// them, then they really have the same priority as this render.
|
||||
// So we'll pick it back up the very next render pass once we've had
|
||||
// an opportunity to yield for paint.
|
||||
// to get it started back up to attempt the next item. While in terms
|
||||
// of priority this work has the same priority as this current render,
|
||||
// it's not part of the same transition once the transition has
|
||||
// committed. If it's sync, we still want to yield so that it can be
|
||||
// painted. Conceptually, this is really the same as pinging.
|
||||
// We can use any RetryLane even if it's the one currently rendering
|
||||
// since we're leaving it behind on this node.
|
||||
workInProgress.lanes = SomeRetryLane;
|
||||
if (enableSchedulerTracing) {
|
||||
markSpawnedWork(SomeRetryLane);
|
||||
@@ -1420,6 +1159,7 @@ function completeWork(
|
||||
const next = renderState.tail;
|
||||
renderState.rendering = next;
|
||||
renderState.tail = next.sibling;
|
||||
renderState.lastEffect = workInProgress.lastEffect;
|
||||
renderState.renderingStartTime = now();
|
||||
next.sibling = null;
|
||||
|
||||
@@ -1437,10 +1177,8 @@ function completeWork(
|
||||
}
|
||||
pushSuspenseContext(workInProgress, suspenseContext);
|
||||
// Do a pass over the next row.
|
||||
// Don't bubble properties in this case.
|
||||
return next;
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
case FundamentalComponent: {
|
||||
@@ -1468,7 +1206,6 @@ function completeWork(
|
||||
): any): Instance);
|
||||
fundamentalInstance.instance = instance;
|
||||
if (fundamentalImpl.reconcileChildren === false) {
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
appendAllChildren(instance, workInProgress, false, false);
|
||||
@@ -1491,7 +1228,6 @@ function completeWork(
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
@@ -1514,7 +1250,6 @@ function completeWork(
|
||||
markRef(workInProgress);
|
||||
}
|
||||
}
|
||||
bubbleProperties(workInProgress);
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
@@ -1522,30 +1257,19 @@ function completeWork(
|
||||
case OffscreenComponent:
|
||||
case LegacyHiddenComponent: {
|
||||
popRenderLanes(workInProgress);
|
||||
const nextState: OffscreenState | null = workInProgress.memoizedState;
|
||||
const nextIsHidden = nextState !== null;
|
||||
|
||||
if (current !== null) {
|
||||
const nextState: OffscreenState | null = workInProgress.memoizedState;
|
||||
const prevState: OffscreenState | null = current.memoizedState;
|
||||
|
||||
const prevIsHidden = prevState !== null;
|
||||
const nextIsHidden = nextState !== null;
|
||||
if (
|
||||
prevIsHidden !== nextIsHidden &&
|
||||
newProps.mode !== 'unstable-defer-without-hiding'
|
||||
) {
|
||||
workInProgress.flags |= Update | Visibility;
|
||||
workInProgress.flags |= Update;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bubble properties for hidden children.
|
||||
if (
|
||||
!nextIsHidden ||
|
||||
includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) ||
|
||||
(workInProgress.mode & ConcurrentMode) === NoMode
|
||||
) {
|
||||
bubbleProperties(workInProgress);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,10 @@ import {
|
||||
enableSchedulingProfiler,
|
||||
enableNewReconciler,
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
enableDoubleInvokingEffects,
|
||||
enableUseRefAccessWarning,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
@@ -54,9 +48,6 @@ import {readContext} from './ReactFiberNewContext.new';
|
||||
import {
|
||||
Update as UpdateEffect,
|
||||
Passive as PassiveEffect,
|
||||
PassiveStatic as PassiveStaticEffect,
|
||||
MountLayoutDev as MountLayoutDevEffect,
|
||||
MountPassiveDev as MountPassiveDevEffect,
|
||||
} from './ReactFiberFlags';
|
||||
import {
|
||||
HasEffect as HookHasEffect,
|
||||
@@ -475,20 +466,7 @@ export function bailoutHooks(
|
||||
lanes: Lanes,
|
||||
) {
|
||||
workInProgress.updateQueue = current.updateQueue;
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags &= ~(
|
||||
MountPassiveDevEffect |
|
||||
PassiveEffect |
|
||||
MountLayoutDevEffect |
|
||||
UpdateEffect
|
||||
);
|
||||
} else {
|
||||
workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
|
||||
}
|
||||
workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
|
||||
current.lanes = removeLanes(current.lanes, lanes);
|
||||
}
|
||||
|
||||
@@ -1320,30 +1298,16 @@ function mountEffect(
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
|
||||
if (typeof jest !== 'undefined') {
|
||||
if ('undefined' !== typeof jest) {
|
||||
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
} else {
|
||||
return mountEffectImpl(
|
||||
PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
return mountEffectImpl(
|
||||
UpdateEffect | PassiveEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
|
||||
function updateEffect(
|
||||
@@ -1352,31 +1316,23 @@ function updateEffect(
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
|
||||
if (typeof jest !== 'undefined') {
|
||||
if ('undefined' !== typeof jest) {
|
||||
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
|
||||
}
|
||||
}
|
||||
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
|
||||
return updateEffectImpl(
|
||||
UpdateEffect | PassiveEffect,
|
||||
HookPassive,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
|
||||
function mountLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountLayoutDevEffect | UpdateEffect,
|
||||
HookLayout,
|
||||
create,
|
||||
deps,
|
||||
);
|
||||
} else {
|
||||
return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
|
||||
}
|
||||
return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
|
||||
}
|
||||
|
||||
function updateLayoutEffect(
|
||||
@@ -1435,25 +1391,12 @@ function mountImperativeHandle<T>(
|
||||
const effectDeps =
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
||||
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountLayoutDevEffect | UpdateEffect,
|
||||
HookLayout,
|
||||
imperativeHandleEffect.bind(null, create, ref),
|
||||
effectDeps,
|
||||
);
|
||||
} else {
|
||||
return mountEffectImpl(
|
||||
UpdateEffect,
|
||||
HookLayout,
|
||||
imperativeHandleEffect.bind(null, create, ref),
|
||||
effectDeps,
|
||||
);
|
||||
}
|
||||
return mountEffectImpl(
|
||||
UpdateEffect,
|
||||
HookLayout,
|
||||
imperativeHandleEffect.bind(null, create, ref),
|
||||
effectDeps,
|
||||
);
|
||||
}
|
||||
|
||||
function updateImperativeHandle<T>(
|
||||
@@ -1734,12 +1677,7 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
|
||||
const setId = mountState(id)[1];
|
||||
|
||||
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
currentlyRenderingFiber.flags |=
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect;
|
||||
} else {
|
||||
currentlyRenderingFiber.flags |= PassiveEffect | PassiveStaticEffect;
|
||||
}
|
||||
currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
|
||||
pushEffect(
|
||||
HookHasEffect | HookPassive,
|
||||
() => {
|
||||
@@ -1855,7 +1793,7 @@ function dispatchAction<S, A>(
|
||||
}
|
||||
if (__DEV__) {
|
||||
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
|
||||
if (typeof jest !== 'undefined') {
|
||||
if ('undefined' !== typeof jest) {
|
||||
warnIfNotScopedWithMatchingAct(fiber);
|
||||
warnIfNotCurrentlyActingUpdatesInDev(fiber);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
HostRoot,
|
||||
SuspenseComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {Deletion, Hydrating, Placement} from './ReactFiberFlags';
|
||||
import {Deletion, Placement, Hydrating} from './ReactFiberFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
@@ -124,14 +124,18 @@ function deleteHydratableInstance(
|
||||
const childToDelete = createFiberFromHostInstanceForDeletion();
|
||||
childToDelete.stateNode = instance;
|
||||
childToDelete.return = returnFiber;
|
||||
childToDelete.flags = Deletion;
|
||||
|
||||
const deletions = returnFiber.deletions;
|
||||
if (deletions === null) {
|
||||
returnFiber.deletions = [childToDelete];
|
||||
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
|
||||
returnFiber.flags |= 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 {
|
||||
deletions.push(childToDelete);
|
||||
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ export type SuspenseListRenderState = {|
|
||||
tail: null | Fiber,
|
||||
// Tail insertions setting.
|
||||
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(
|
||||
|
||||
@@ -185,6 +185,8 @@ function throwException(
|
||||
) {
|
||||
// The source fiber did not complete.
|
||||
sourceFiber.flags |= Incomplete;
|
||||
// Its effect list is no longer valid.
|
||||
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
|
||||
|
||||
if (
|
||||
value !== null &&
|
||||
|
||||
@@ -13,14 +13,15 @@ import type {Lanes, Lane} from './ReactFiberLane';
|
||||
import type {ReactPriorityLevel} from './ReactInternalTypes';
|
||||
import type {Interaction} from 'scheduler/src/Tracing';
|
||||
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,
|
||||
enableSuspenseServerRenderer,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
enableProfilerTimer,
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableProfilerNestedUpdateScheduledHook,
|
||||
enableSchedulerTracing,
|
||||
@@ -29,7 +30,7 @@ import {
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
enableDoubleInvokingEffects,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import invariant from 'shared/invariant';
|
||||
@@ -49,10 +50,6 @@ import {
|
||||
flushSyncCallbackQueue,
|
||||
scheduleSyncCallback,
|
||||
} from './SchedulerWithReactIntegration.new';
|
||||
import {
|
||||
NoFlags as NoHookEffect,
|
||||
Passive as HookPassive,
|
||||
} from './ReactHookEffectTags';
|
||||
import {
|
||||
logCommitStarted,
|
||||
logCommitStopped,
|
||||
@@ -81,11 +78,13 @@ import * as Scheduler from 'scheduler';
|
||||
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
|
||||
|
||||
import {
|
||||
prepareForCommit,
|
||||
resetAfterCommit,
|
||||
scheduleTimeout,
|
||||
cancelTimeout,
|
||||
noTimeout,
|
||||
warnsIfNotActing,
|
||||
beforeActiveInstanceBlur,
|
||||
afterActiveInstanceBlur,
|
||||
clearContainer,
|
||||
} from './ReactFiberHostConfig';
|
||||
@@ -111,20 +110,29 @@ import {
|
||||
ForwardRef,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
OffscreenComponent,
|
||||
LegacyHiddenComponent,
|
||||
ScopeComponent,
|
||||
Profiler,
|
||||
} from './ReactWorkTags';
|
||||
import {LegacyRoot} from './ReactRootTags';
|
||||
import {
|
||||
NoFlags,
|
||||
PerformedWork,
|
||||
Placement,
|
||||
PassiveStatic,
|
||||
Update,
|
||||
PlacementAndUpdate,
|
||||
Deletion,
|
||||
Ref,
|
||||
ContentReset,
|
||||
Snapshot,
|
||||
Callback,
|
||||
Passive,
|
||||
PassiveUnmountPendingDev,
|
||||
Incomplete,
|
||||
HostEffectMask,
|
||||
Hydrating,
|
||||
BeforeMutationMask,
|
||||
MutationMask,
|
||||
LayoutMask,
|
||||
PassiveMask,
|
||||
HydratingAndUpdate,
|
||||
} from './ReactFiberFlags';
|
||||
import {
|
||||
NoLanePriority,
|
||||
@@ -136,6 +144,7 @@ import {
|
||||
NoLane,
|
||||
SyncLane,
|
||||
SyncBatchedLane,
|
||||
OffscreenLane,
|
||||
NoTimestamp,
|
||||
findUpdateLane,
|
||||
findTransitionLane,
|
||||
@@ -175,12 +184,16 @@ import {
|
||||
createClassErrorUpdate,
|
||||
} from './ReactFiberThrow.new';
|
||||
import {
|
||||
commitBeforeMutationEffects,
|
||||
commitMutationEffects,
|
||||
commitLayoutEffects,
|
||||
commitPassiveMountEffects,
|
||||
commitPassiveUnmountEffects,
|
||||
commitDoubleInvokeEffectsInDEV,
|
||||
commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
|
||||
commitLifeCycles as commitLayoutEffectOnFiber,
|
||||
commitPlacement,
|
||||
commitWork,
|
||||
commitDeletion,
|
||||
commitDetachRef,
|
||||
commitAttachRef,
|
||||
commitPassiveEffectDurations,
|
||||
commitResetTextContent,
|
||||
isSuspenseBoundaryBeingHidden,
|
||||
} from './ReactFiberCommitWork.new';
|
||||
import {enqueueUpdate} from './ReactUpdateQueue.new';
|
||||
import {resetContextDependencies} from './ReactFiberNewContext.new';
|
||||
@@ -199,7 +212,9 @@ import {
|
||||
import {
|
||||
markNestedUpdateScheduled,
|
||||
recordCommitTime,
|
||||
recordPassiveEffectDuration,
|
||||
resetNestedUpdateFlag,
|
||||
startPassiveEffectTimer,
|
||||
startProfilerTimer,
|
||||
stopProfilerTimerIfRunningAndRecordDelta,
|
||||
syncNestedUpdateFlag,
|
||||
@@ -224,6 +239,7 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
|
||||
|
||||
// Used by `act`
|
||||
import enqueueTask from 'shared/enqueueTask';
|
||||
import {doesFiberContain} from './ReactFiberTreeReflection';
|
||||
|
||||
const ceil = Math.ceil;
|
||||
|
||||
@@ -261,10 +277,6 @@ let workInProgress: Fiber | null = null;
|
||||
// The lanes we're rendering
|
||||
let workInProgressRootRenderLanes: Lanes = NoLanes;
|
||||
|
||||
// Only used when enableProfilerNestedUpdateScheduledHook is true;
|
||||
// to track which root is currently committing layout effects.
|
||||
let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
|
||||
|
||||
// Stack that allows components to change the render lanes for its subtree
|
||||
// This is a superset of the lanes we started working on at the root. The only
|
||||
// case where it's different from `workInProgressRootRenderLanes` is when we
|
||||
@@ -273,7 +285,7 @@ let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
|
||||
//
|
||||
// Most things in the work loop should deal with workInProgressRootRenderLanes.
|
||||
// Most things in begin/complete phases should deal with subtreeRenderLanes.
|
||||
export let subtreeRenderLanes: Lanes = NoLanes;
|
||||
let subtreeRenderLanes: Lanes = NoLanes;
|
||||
const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
|
||||
|
||||
// Whether to root completed, errored, suspended, etc.
|
||||
@@ -315,13 +327,22 @@ export function getRenderTargetTime(): number {
|
||||
return workInProgressRootRenderTargetTime;
|
||||
}
|
||||
|
||||
let nextEffect: Fiber | null = null;
|
||||
let hasUncaughtError = false;
|
||||
let firstUncaughtError = null;
|
||||
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
|
||||
|
||||
// Only used when enableProfilerNestedUpdateScheduledHook is true;
|
||||
// to track which root is currently committing layout effects.
|
||||
let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null;
|
||||
|
||||
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;
|
||||
|
||||
@@ -351,6 +372,9 @@ let currentEventPendingLanes: Lanes = NoLanes;
|
||||
// We warn about state updates for unmounted components differently in this case.
|
||||
let isFlushingPassiveEffects = false;
|
||||
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
export function getWorkInProgressRoot(): FiberRoot | null {
|
||||
return workInProgressRoot;
|
||||
}
|
||||
@@ -1717,6 +1741,47 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
|
||||
workInProgress = next;
|
||||
return;
|
||||
}
|
||||
|
||||
resetChildLanes(completedWork);
|
||||
|
||||
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 > PerformedWork) {
|
||||
if (returnFiber.lastEffect !== null) {
|
||||
returnFiber.lastEffect.nextEffect = completedWork;
|
||||
} else {
|
||||
returnFiber.firstEffect = completedWork;
|
||||
}
|
||||
returnFiber.lastEffect = completedWork;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This fiber did not complete because something threw. Pop values off
|
||||
// the stack without entering the complete phase. If this is a boundary,
|
||||
@@ -1753,10 +1818,9 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
|
||||
}
|
||||
|
||||
if (returnFiber !== null) {
|
||||
// Mark the parent fiber as incomplete
|
||||
// Mark the parent fiber as incomplete and clear its effect list.
|
||||
returnFiber.firstEffect = returnFiber.lastEffect = null;
|
||||
returnFiber.flags |= Incomplete;
|
||||
returnFiber.subtreeFlags = NoFlags;
|
||||
returnFiber.deletions = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1778,6 +1842,81 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
|
||||
}
|
||||
}
|
||||
|
||||
function resetChildLanes(completedWork: Fiber) {
|
||||
if (
|
||||
// TODO: Move this check out of the hot path by moving `resetChildLanes`
|
||||
// to switch statement in `completeWork`.
|
||||
(completedWork.tag === LegacyHiddenComponent ||
|
||||
completedWork.tag === OffscreenComponent) &&
|
||||
completedWork.memoizedState !== null &&
|
||||
!includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) &&
|
||||
(completedWork.mode & ConcurrentMode) !== NoLanes
|
||||
) {
|
||||
// The children of this component are hidden. Don't bubble their
|
||||
// expiration times.
|
||||
return;
|
||||
}
|
||||
|
||||
let newChildLanes = NoLanes;
|
||||
|
||||
// Bubble up the earliest expiration time.
|
||||
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
|
||||
// In profiling mode, resetChildExpirationTime is also used to reset
|
||||
// profiler durations.
|
||||
let actualDuration = completedWork.actualDuration;
|
||||
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
|
||||
|
||||
// When a fiber is cloned, its actualDuration is reset to 0. This value will
|
||||
// only be updated if work is done on the fiber (i.e. it doesn't bailout).
|
||||
// When work is done, it should bubble to the parent's actualDuration. If
|
||||
// the fiber has not been cloned though, (meaning no work was done), then
|
||||
// this value will reflect the amount of time spent working on a previous
|
||||
// render. In that case it should not bubble. We determine whether it was
|
||||
// cloned by comparing the child pointer.
|
||||
const shouldBubbleActualDurations =
|
||||
completedWork.alternate === null ||
|
||||
completedWork.child !== completedWork.alternate.child;
|
||||
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
if (shouldBubbleActualDurations) {
|
||||
actualDuration += child.actualDuration;
|
||||
}
|
||||
treeBaseDuration += child.treeBaseDuration;
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
const isTimedOutSuspense =
|
||||
completedWork.tag === SuspenseComponent &&
|
||||
completedWork.memoizedState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = completedWork.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
|
||||
completedWork.actualDuration = actualDuration;
|
||||
completedWork.treeBaseDuration = treeBaseDuration;
|
||||
} else {
|
||||
let child = completedWork.child;
|
||||
while (child !== null) {
|
||||
newChildLanes = mergeLanes(
|
||||
newChildLanes,
|
||||
mergeLanes(child.lanes, child.childLanes),
|
||||
);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
completedWork.childLanes = newChildLanes;
|
||||
}
|
||||
|
||||
function commitRoot(root) {
|
||||
const renderPriorityLevel = getCurrentPriorityLevel();
|
||||
runWithPriority(
|
||||
@@ -1871,37 +2010,25 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// times out.
|
||||
}
|
||||
|
||||
// If there are pending passive effects, schedule a callback to process them.
|
||||
// Do this as early as possible, so it is queued before anything else that
|
||||
// might get scheduled in the commit phase. (See #16714.)
|
||||
const rootDoesHavePassiveEffects =
|
||||
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
|
||||
(finishedWork.flags & PassiveMask) !== NoFlags;
|
||||
if (rootDoesHavePassiveEffects) {
|
||||
rootWithPendingPassiveEffects = root;
|
||||
pendingPassiveEffectsLanes = lanes;
|
||||
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
|
||||
scheduleCallback(NormalSchedulerPriority, () => {
|
||||
flushPassiveEffects();
|
||||
return null;
|
||||
});
|
||||
// Get the list of effects.
|
||||
let firstEffect;
|
||||
if (finishedWork.flags > PerformedWork) {
|
||||
// A fiber's effect list consists only of its children, not itself. So if
|
||||
// the root has an effect, we need to add it to the end of the list. The
|
||||
// resulting list is the set that would belong to the root's parent, if it
|
||||
// had one; that is, all the effects in the tree including the root.
|
||||
if (finishedWork.lastEffect !== null) {
|
||||
finishedWork.lastEffect.nextEffect = finishedWork;
|
||||
firstEffect = finishedWork.firstEffect;
|
||||
} else {
|
||||
firstEffect = finishedWork;
|
||||
}
|
||||
} else {
|
||||
// There is no effect on the root.
|
||||
firstEffect = finishedWork.firstEffect;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (firstEffect !== null) {
|
||||
let previousLanePriority;
|
||||
if (decoupleUpdatePriorityFromScheduler) {
|
||||
previousLanePriority = getCurrentUpdateLanePriority();
|
||||
@@ -1922,10 +2049,32 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// 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
|
||||
// getSnapshotBeforeUpdate is called.
|
||||
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
|
||||
root,
|
||||
finishedWork,
|
||||
);
|
||||
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
||||
shouldFireAfterActiveInstanceBlur = false;
|
||||
|
||||
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) {
|
||||
// Mark the current commit time to be shared by all Profilers in this
|
||||
@@ -1934,11 +2083,38 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
|
||||
// Track the root here, rather than in commitLayoutEffects(), because of ref setters.
|
||||
// Updates scheduled during ref detachment should also be flagged.
|
||||
rootCommittingMutationOrLayoutEffects = root;
|
||||
}
|
||||
|
||||
// The next phase is the mutation phase, where we mutate the host tree.
|
||||
commitMutationEffects(finishedWork, root, renderPriorityLevel);
|
||||
nextEffect = firstEffect;
|
||||
do {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(
|
||||
null,
|
||||
commitMutationEffects,
|
||||
null,
|
||||
root,
|
||||
renderPriorityLevel,
|
||||
);
|
||||
if (hasCaughtError()) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
commitMutationEffects(root, renderPriorityLevel);
|
||||
} catch (error) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
if (shouldFireAfterActiveInstanceBlur) {
|
||||
afterActiveInstanceBlur();
|
||||
@@ -1954,26 +2130,28 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// The next phase is the layout phase, where we call effects that read
|
||||
// the host tree after it's been mutated. The idiomatic use case for this is
|
||||
// layout, but class component lifecycles also fire here for legacy reasons.
|
||||
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
logLayoutEffectsStarted(lanes);
|
||||
nextEffect = firstEffect;
|
||||
do {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
|
||||
if (hasCaughtError()) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
commitLayoutEffects(root, lanes);
|
||||
} catch (error) {
|
||||
invariant(nextEffect !== null, 'Should be working on an effect.');
|
||||
captureCommitPhaseError(nextEffect, error);
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markLayoutEffectsStarted(lanes);
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
commitLayoutEffects(finishedWork, root);
|
||||
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
logLayoutEffectsStopped();
|
||||
}
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markLayoutEffectsStopped();
|
||||
}
|
||||
nextEffect = null;
|
||||
|
||||
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
|
||||
rootCommittingMutationOrLayoutEffects = null;
|
||||
@@ -2003,6 +2181,30 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
|
||||
|
||||
if (rootDoesHavePassiveEffects) {
|
||||
// This commit has passive effects. Stash a reference to them. But don't
|
||||
// schedule a callback until after flushing layout work.
|
||||
rootDoesHavePassiveEffects = false;
|
||||
rootWithPendingPassiveEffects = root;
|
||||
pendingPassiveEffectsLanes = lanes;
|
||||
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.
|
||||
nextEffect = firstEffect;
|
||||
while (nextEffect !== null) {
|
||||
const nextNextEffect = nextEffect.nextEffect;
|
||||
nextEffect.nextEffect = null;
|
||||
if (nextEffect.flags & Deletion) {
|
||||
detachFiberAfterEffects(nextEffect);
|
||||
}
|
||||
nextEffect = nextNextEffect;
|
||||
}
|
||||
}
|
||||
|
||||
// Read this again, since an effect might have updated it
|
||||
remainingLanes = root.pendingLanes;
|
||||
|
||||
@@ -2028,14 +2230,8 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
legacyErrorBoundariesThatAlreadyFailed = null;
|
||||
}
|
||||
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
commitDoubleInvokeEffectsInDEV(root.current, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
if (!rootDidHavePassiveEffects) {
|
||||
// If there are no passive effects, then we can complete the pending interactions.
|
||||
// Otherwise, we'll wait until after the passive effects are flushed.
|
||||
// Wait to do this until after remaining work has been scheduled,
|
||||
@@ -2112,6 +2308,181 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
|
||||
// TODO: Should probably move the bulk of this function to commitWork.
|
||||
while (nextEffect !== null) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
|
||||
const flags = nextEffect.flags;
|
||||
|
||||
if (flags & ContentReset) {
|
||||
commitResetTextContent(nextEffect);
|
||||
}
|
||||
|
||||
if (flags & Ref) {
|
||||
const current = nextEffect.alternate;
|
||||
if (current !== null) {
|
||||
commitDetachRef(current);
|
||||
}
|
||||
if (enableScopeAPI) {
|
||||
// TODO: This is a temporary solution that allowed us to transition away
|
||||
// from React Flare on www.
|
||||
if (nextEffect.tag === ScopeComponent) {
|
||||
commitAttachRef(nextEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The following switch statement is only concerned about placement,
|
||||
// updates, and deletions. To avoid needing to add a case for every possible
|
||||
// bitmap value, we remove the secondary effects from the effect tag and
|
||||
// switch on that value.
|
||||
const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
|
||||
switch (primaryFlags) {
|
||||
case Placement: {
|
||||
commitPlacement(nextEffect);
|
||||
// Clear the "placement" from effect tag so that we know that this is
|
||||
// inserted, before any life-cycles like componentDidMount gets called.
|
||||
// TODO: findDOMNode doesn't rely on this any more but isMounted does
|
||||
// and isMounted is deprecated anyway so we should be able to kill this.
|
||||
nextEffect.flags &= ~Placement;
|
||||
break;
|
||||
}
|
||||
case PlacementAndUpdate: {
|
||||
// Placement
|
||||
commitPlacement(nextEffect);
|
||||
// Clear the "placement" from effect tag so that we know that this is
|
||||
// inserted, before any life-cycles like componentDidMount gets called.
|
||||
nextEffect.flags &= ~Placement;
|
||||
|
||||
// Update
|
||||
const current = nextEffect.alternate;
|
||||
commitWork(current, nextEffect);
|
||||
break;
|
||||
}
|
||||
case Hydrating: {
|
||||
nextEffect.flags &= ~Hydrating;
|
||||
break;
|
||||
}
|
||||
case HydratingAndUpdate: {
|
||||
nextEffect.flags &= ~Hydrating;
|
||||
|
||||
// Update
|
||||
const current = nextEffect.alternate;
|
||||
commitWork(current, nextEffect);
|
||||
break;
|
||||
}
|
||||
case Update: {
|
||||
const current = nextEffect.alternate;
|
||||
commitWork(current, nextEffect);
|
||||
break;
|
||||
}
|
||||
case Deletion: {
|
||||
commitDeletion(root, nextEffect, renderPriorityLevel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resetCurrentDebugFiberInDEV();
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
|
||||
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
logLayoutEffectsStarted(committedLanes);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markLayoutEffectsStarted(committedLanes);
|
||||
}
|
||||
|
||||
// TODO: Should probably move the bulk of this function to commitWork.
|
||||
while (nextEffect !== null) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
|
||||
const flags = nextEffect.flags;
|
||||
|
||||
if (flags & (Update | Callback)) {
|
||||
const current = nextEffect.alternate;
|
||||
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
|
||||
}
|
||||
|
||||
if (enableScopeAPI) {
|
||||
// TODO: This is a temporary solution that allowed us to transition away
|
||||
// from React Flare on www.
|
||||
if (flags & Ref && nextEffect.tag !== ScopeComponent) {
|
||||
commitAttachRef(nextEffect);
|
||||
}
|
||||
} else {
|
||||
if (flags & Ref) {
|
||||
commitAttachRef(nextEffect);
|
||||
}
|
||||
}
|
||||
|
||||
resetCurrentDebugFiberInDEV();
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
logLayoutEffectsStopped();
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markLayoutEffectsStopped();
|
||||
}
|
||||
}
|
||||
|
||||
export function flushPassiveEffects(): boolean {
|
||||
// Returns whether passive effects were flushed.
|
||||
if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
|
||||
@@ -2137,6 +2508,59 @@ export function flushPassiveEffects(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
pendingPassiveProfilerEffects.push(fiber);
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
rootDoesHavePassiveEffects = true;
|
||||
scheduleCallback(NormalSchedulerPriority, () => {
|
||||
flushPassiveEffects();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.flags |= PassiveUnmountPendingDev;
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.flags |= PassiveUnmountPendingDev;
|
||||
}
|
||||
}
|
||||
if (!rootDoesHavePassiveEffects) {
|
||||
rootDoesHavePassiveEffects = true;
|
||||
scheduleCallback(NormalSchedulerPriority, () => {
|
||||
flushPassiveEffects();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function invokePassiveEffectCreate(effect: HookEffect): void {
|
||||
const create = effect.create;
|
||||
effect.destroy = create();
|
||||
}
|
||||
|
||||
function flushPassiveEffectsImpl() {
|
||||
if (rootWithPendingPassiveEffects === null) {
|
||||
return false;
|
||||
@@ -2176,8 +2600,147 @@ 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.
|
||||
commitPassiveUnmountEffects(root.current);
|
||||
commitPassiveMountEffects(root, root.current);
|
||||
|
||||
// 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.flags &= ~PassiveUnmountPendingDev;
|
||||
const alternate = fiber.alternate;
|
||||
if (alternate !== null) {
|
||||
alternate.flags &= ~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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This currently assumes there are no passive effects on the root fiber
|
||||
// because the root is not part of its own effect list.
|
||||
// This could change in the future.
|
||||
let effect = root.current.firstEffect;
|
||||
while (effect !== null) {
|
||||
const nextNextEffect = effect.nextEffect;
|
||||
// Remove nextEffect pointer to assist GC
|
||||
effect.nextEffect = null;
|
||||
if (effect.flags & Deletion) {
|
||||
detachFiberAfterEffects(effect);
|
||||
}
|
||||
effect = nextNextEffect;
|
||||
}
|
||||
|
||||
if (enableProfilerTimer && enableProfilerCommitHooks) {
|
||||
const profilerEffects = pendingPassiveProfilerEffects;
|
||||
pendingPassiveProfilerEffects = [];
|
||||
for (let i = 0; i < profilerEffects.length; i++) {
|
||||
const fiber = ((profilerEffects[i]: any): Fiber);
|
||||
commitPassiveEffectDurations(root, fiber);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
popInteractions(((prevInteractions: any): Set<Interaction>));
|
||||
finishPendingInteractions(root, lanes);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
isFlushingPassiveEffects = false;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (enableDebugTracing) {
|
||||
@@ -2189,19 +2752,6 @@ function flushPassiveEffectsImpl() {
|
||||
markPassiveEffectsStopped();
|
||||
}
|
||||
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
commitDoubleInvokeEffectsInDEV(root.current, true);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
isFlushingPassiveEffects = false;
|
||||
}
|
||||
|
||||
if (enableSchedulerTracing) {
|
||||
popInteractions(((prevInteractions: any): Set<Interaction>));
|
||||
finishPendingInteractions(root, lanes);
|
||||
}
|
||||
|
||||
executionContext = prevExecutionContext;
|
||||
|
||||
flushSyncCallbackQueue();
|
||||
@@ -2542,24 +3092,10 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((fiber.flags & PassiveStatic) !== NoFlags) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
// If there are pending passive effects unmounts for this Fiber,
|
||||
// we can assume that they would have prevented this update.
|
||||
if ((fiber.flags & PassiveUnmountPendingDev) !== NoFlags) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We show the whole stack but dedupe on the top component's name because
|
||||
@@ -3249,3 +3785,8 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function detachFiberAfterEffects(fiber: Fiber): void {
|
||||
fiber.sibling = null;
|
||||
fiber.stateNode = null;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ if (__DEV__) {
|
||||
fiber: Fiber,
|
||||
instance: any,
|
||||
) => {
|
||||
// Dedup strategy: Warn once per component.
|
||||
// Dedupe strategy: Warn once per component.
|
||||
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,13 +165,13 @@ export function cancelCallback(callbackNode: mixed) {
|
||||
}
|
||||
}
|
||||
|
||||
export function flushSyncCallbackQueue(): boolean {
|
||||
export function flushSyncCallbackQueue() {
|
||||
if (immediateQueueCallbackNode !== null) {
|
||||
const node = immediateQueueCallbackNode;
|
||||
immediateQueueCallbackNode = null;
|
||||
Scheduler_cancelCallback(node);
|
||||
}
|
||||
return flushSyncCallbackQueueImpl();
|
||||
flushSyncCallbackQueueImpl();
|
||||
}
|
||||
|
||||
function flushSyncCallbackQueueImpl() {
|
||||
@@ -237,8 +237,5 @@ function flushSyncCallbackQueueImpl() {
|
||||
isFlushingSyncQueue = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-13
@@ -10,7 +10,6 @@
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let act;
|
||||
@@ -19,13 +18,20 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
ReactFeatureFlags.enableDoubleInvokingEffects = __VARIANT__;
|
||||
act = ReactTestRenderer.unstable_concurrentAct;
|
||||
});
|
||||
|
||||
function supportsDoubleInvokeEffects() {
|
||||
return gate(
|
||||
flags =>
|
||||
flags.build === 'development' &&
|
||||
flags.enableDoubleInvokingEffects &&
|
||||
flags.dfsEffectsRefactor,
|
||||
);
|
||||
}
|
||||
|
||||
it('should not double invoke effects in legacy mode', () => {
|
||||
function App({text}) {
|
||||
React.useEffect(() => {
|
||||
@@ -73,7 +79,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'useLayoutEffect mount',
|
||||
'useEffect mount',
|
||||
@@ -132,7 +138,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'useEffect One mount',
|
||||
'useEffect Two mount',
|
||||
@@ -193,7 +199,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'useLayoutEffect One mount',
|
||||
'useLayoutEffect Two mount',
|
||||
@@ -250,7 +256,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'useLayoutEffect mount',
|
||||
'useEffect mount',
|
||||
@@ -308,7 +314,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'componentDidMount',
|
||||
'componentWillUnmount',
|
||||
@@ -345,7 +351,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'componentDidMount',
|
||||
'componentWillUnmount',
|
||||
@@ -420,7 +426,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'mount',
|
||||
'useLayoutEffect mount',
|
||||
@@ -485,7 +491,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'App useLayoutEffect mount',
|
||||
'App useEffect mount',
|
||||
@@ -505,7 +511,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
_setShowChild(true);
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'App useLayoutEffect unmount',
|
||||
'Child useLayoutEffect mount',
|
||||
@@ -573,7 +579,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
if (supportsDoubleInvokeEffects()) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'componentDidMount',
|
||||
'useLayoutEffect mount',
|
||||
@@ -152,7 +152,7 @@ describe('ReactDOMTracing', () => {
|
||||
onInteractionScheduledWorkCompleted,
|
||||
).toHaveBeenLastNotifiedOfInteraction(interaction);
|
||||
|
||||
if (gate(flags => flags.new)) {
|
||||
if (gate(flags => flags.dfsEffectsRefactor)) {
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
} else {
|
||||
// TODO: This is 4 instead of 3 because this update was scheduled at
|
||||
@@ -310,7 +310,7 @@ describe('ReactDOMTracing', () => {
|
||||
expect(
|
||||
onInteractionScheduledWorkCompleted,
|
||||
).toHaveBeenLastNotifiedOfInteraction(interaction);
|
||||
if (gate(flags => flags.new)) {
|
||||
if (gate(flags => flags.dfsEffectsRefactor)) {
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
} else {
|
||||
// TODO: This is 4 instead of 3 because this update was scheduled at
|
||||
|
||||
@@ -368,7 +368,7 @@ describe('Profiler', () => {
|
||||
|
||||
renderer.update(<App />);
|
||||
|
||||
if (gate(flags => flags.new)) {
|
||||
if (gate(flags => flags.dfsEffectsRefactor)) {
|
||||
// None of the Profiler's subtree was rendered because App bailed out before the Profiler.
|
||||
// So we expect onRender not to be called.
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
@@ -4292,7 +4292,7 @@ describe('Profiler', () => {
|
||||
// because the resolved suspended subtree doesn't contain any passive effects.
|
||||
// If <AsyncComponentWithCascadingWork> or its decendents had a passive effect,
|
||||
// onPostCommit would be called again.
|
||||
if (gate(flags => flags.new)) {
|
||||
if (gate(flags => flags.dfsEffectsRefactor)) {
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
} else {
|
||||
expect(Scheduler).toFlushAndYield(['onPostCommit']);
|
||||
@@ -4783,7 +4783,8 @@ describe('Profiler', () => {
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
// @gate new
|
||||
// @gate dfsEffectsRefactor
|
||||
// @gate enableDoubleInvokingEffects
|
||||
it('double invoking does not disconnect wrapped async work', () => {
|
||||
ReactFeatureFlags.enableDoubleInvokingEffects = true;
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ const environmentFlags = {
|
||||
|
||||
// Use this for tests that are known to be broken.
|
||||
FIXME: false,
|
||||
|
||||
// Turn this flag back on (or delete) once the effect list is removed in favor
|
||||
// of a depth-first traversal using `subtreeTags`.
|
||||
dfsEffectsRefactor: false,
|
||||
};
|
||||
|
||||
function getTestFlags() {
|
||||
|
||||
Reference in New Issue
Block a user