Align StrictMode behaviour with production (#25049)
* Skip double invoking effects in Offscreen * Run yarn replace-fork * Use executionContext to disable profiler timer * Restructure recursion into two functions * Fix ReactStrictMode test * Use gate pragma in ReacetOffscreenStrictMode test * Set and reset current debug fiber in dev * Skip over paths that don't include any insertions * Extract common logic to check for profiling to a helper function * Remove hasPassiveEffects flag from StrictMode * Fix flow issues * Revert "Skip over paths that don't include any insertions"
Samuel Susla committed
Aug 23, 2022 at 18:19 UTC
5d1ce651393524639a3b4b1e861a0413a4d25629
10 files changed
+376
-557
packages/react-reconciler/src/ReactChildFiber.new.js
+9
-4
@@ -13,7 +13,12 @@ import type {Fiber} from './ReactInternalTypes';
13
import type {Lanes} from './ReactFiberLane.new';
14
15
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
16
-import {Placement, ChildDeletion, Forked} from './ReactFiberFlags';
16
+import {
17
+ Placement,
18
+ ChildDeletion,
19
+ Forked,
20
+ PlacementDEV,
21
+} from './ReactFiberFlags';
22
import {
23
getIteratorFn,
24
REACT_ELEMENT_TYPE,
@@ -343,7 +348,7 @@ function ChildReconciler(shouldTrackSideEffects) {
348
const oldIndex = current.index;
349
if (oldIndex < lastPlacedIndex) {
350
// This is a move.
346
- newFiber.flags |= Placement;
351
+ newFiber.flags |= Placement | PlacementDEV;
352
return lastPlacedIndex;
353
} else {
354
// This item can stay in place.
@@ -351,7 +356,7 @@ function ChildReconciler(shouldTrackSideEffects) {
356
}
357
} else {
358
// This is an insertion.
354
- newFiber.flags |= Placement;
359
+ newFiber.flags |= Placement | PlacementDEV;
360
return lastPlacedIndex;
361
}
362
}
@@ -360,7 +365,7 @@ function ChildReconciler(shouldTrackSideEffects) {
365
// This is simpler for the single child case. We only need to do a
366
// placement for inserting new children.
367
if (shouldTrackSideEffects && newFiber.alternate === null) {
363
- newFiber.flags |= Placement;
368
+ newFiber.flags |= Placement | PlacementDEV;
369
}
370
return newFiber;
371
}
packages/react-reconciler/src/ReactChildFiber.old.js
+9
-4
@@ -13,7 +13,12 @@ import type {Fiber} from './ReactInternalTypes';
13
import type {Lanes} from './ReactFiberLane.old';
14
15
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
16
-import {Placement, ChildDeletion, Forked} from './ReactFiberFlags';
16
+import {
17
+ Placement,
18
+ ChildDeletion,
19
+ Forked,
20
+ PlacementDEV,
21
+} from './ReactFiberFlags';
22
import {
23
getIteratorFn,
24
REACT_ELEMENT_TYPE,
@@ -343,7 +348,7 @@ function ChildReconciler(shouldTrackSideEffects) {
348
const oldIndex = current.index;
349
if (oldIndex < lastPlacedIndex) {
350
// This is a move.
346
- newFiber.flags |= Placement;
351
+ newFiber.flags |= Placement | PlacementDEV;
352
return lastPlacedIndex;
353
} else {
354
// This item can stay in place.
@@ -351,7 +356,7 @@ function ChildReconciler(shouldTrackSideEffects) {
356
}
357
} else {
358
// This is an insertion.
354
- newFiber.flags |= Placement;
359
+ newFiber.flags |= Placement | PlacementDEV;
360
return lastPlacedIndex;
361
}
362
}
@@ -360,7 +365,7 @@ function ChildReconciler(shouldTrackSideEffects) {
365
// This is simpler for the single child case. We only need to do a
366
// placement for inserting new children.
367
if (shouldTrackSideEffects && newFiber.alternate === null) {
363
- newFiber.flags |= Placement;
368
+ newFiber.flags |= Placement | PlacementDEV;
369
}
370
return newFiber;
371
}
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+35
-176
@@ -40,7 +40,6 @@ import {
40
enableSchedulingProfiler,
41
enableSuspenseCallback,
42
enableScopeAPI,
43
- enableStrictEffects,
43
deletedTreeCleanUpLevel,
44
enableUpdaterTracking,
45
enableCache,
@@ -148,6 +147,9 @@ import {
147
addMarkerProgressCallbackToPendingTransition,
148
addMarkerCompleteCallbackToPendingTransition,
149
setIsRunningInsertionEffect,
150
+ getExecutionContext,
151
+ CommitContext,
152
+ NoContext,
153
} from './ReactFiberWorkLoop.new';
154
import {
155
NoFlags as NoHookEffect,
@@ -200,6 +202,15 @@ let nextEffect: Fiber | null = null;
202
let inProgressLanes: Lanes | null = null;
203
let inProgressRoot: FiberRoot | null = null;
204
205
+function shouldProfile(current: Fiber): boolean {
206
+ return (
207
+ enableProfilerTimer &&
208
+ enableProfilerCommitHooks &&
209
+ (current.mode & ProfileMode) !== NoMode &&
210
+ (getExecutionContext() & CommitContext) !== NoContext
211
+ );
212
+}
213
+
214
export function reportUncaughtErrorInDEV(error: mixed) {
215
// Wrapping each small part of the commit phase into a guarded
216
// callback is a bit too slow (https://github.com/facebook/react/pull/21666).
@@ -217,11 +228,7 @@ export function reportUncaughtErrorInDEV(error: mixed) {
228
const callComponentWillUnmountWithTimer = function(current, instance) {
229
instance.props = current.memoizedProps;
230
instance.state = current.memoizedState;
220
- if (
221
- enableProfilerTimer &&
222
- enableProfilerCommitHooks &&
223
- current.mode & ProfileMode
224
- ) {
231
+ if (shouldProfile(current)) {
232
try {
233
startLayoutEffectTimer();
234
instance.componentWillUnmount();
@@ -261,11 +268,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
268
if (typeof ref === 'function') {
269
let retVal;
270
try {
264
- if (
265
- enableProfilerTimer &&
266
- enableProfilerCommitHooks &&
267
- current.mode & ProfileMode
268
- ) {
271
+ if (shouldProfile(current)) {
272
try {
273
startLayoutEffectTimer();
274
retVal = ref(null);
@@ -641,7 +644,11 @@ export function commitPassiveEffectDurations(
644
finishedRoot: FiberRoot,
645
finishedWork: Fiber,
646
): void {
644
- if (enableProfilerTimer && enableProfilerCommitHooks) {
647
+ if (
648
+ enableProfilerTimer &&
649
+ enableProfilerCommitHooks &&
650
+ getExecutionContext() & CommitContext
651
+ ) {
652
// Only Profilers with work in their subtree will have an Update effect scheduled.
653
if ((finishedWork.flags & Update) !== NoFlags) {
654
switch (finishedWork.tag) {
@@ -694,11 +701,7 @@ function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
701
// This is done to prevent sibling component effects from interfering with each other,
702
// e.g. a destroy function in one component should never override a ref set
703
// by a create function in another component during the same commit.
697
- if (
698
- enableProfilerTimer &&
699
- enableProfilerCommitHooks &&
700
- finishedWork.mode & ProfileMode
701
- ) {
704
+ if (shouldProfile(finishedWork)) {
705
try {
706
startLayoutEffectTimer();
707
commitHookEffectListMount(hookFlags, finishedWork);
@@ -751,11 +754,7 @@ function commitClassLayoutLifecycles(
754
}
755
}
756
}
754
- if (
755
- enableProfilerTimer &&
756
- enableProfilerCommitHooks &&
757
- finishedWork.mode & ProfileMode
758
- ) {
757
+ if (shouldProfile(finishedWork)) {
758
try {
759
startLayoutEffectTimer();
760
instance.componentDidMount();
@@ -806,11 +805,7 @@ function commitClassLayoutLifecycles(
805
}
806
}
807
}
809
- if (
810
- enableProfilerTimer &&
811
- enableProfilerCommitHooks &&
812
- finishedWork.mode & ProfileMode
813
- ) {
808
+ if (shouldProfile(finishedWork)) {
809
try {
810
startLayoutEffectTimer();
811
instance.componentDidUpdate(
@@ -892,7 +887,7 @@ function commitHostComponentMount(finishedWork: Fiber) {
887
}
888
889
function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
895
- if (enableProfilerTimer) {
890
+ if (enableProfilerTimer && getExecutionContext() & CommitContext) {
891
try {
892
const {onCommit, onRender} = finishedWork.memoizedProps;
893
const {effectDuration} = finishedWork.stateNode;
@@ -1341,11 +1336,7 @@ function commitAttachRef(finishedWork: Fiber) {
1336
}
1337
if (typeof ref === 'function') {
1338
let retVal;
1344
- if (
1345
- enableProfilerTimer &&
1346
- enableProfilerCommitHooks &&
1347
- finishedWork.mode & ProfileMode
1348
- ) {
1339
+ if (shouldProfile(finishedWork)) {
1340
try {
1341
startLayoutEffectTimer();
1342
retVal = ref(instanceToUse);
@@ -1384,11 +1375,7 @@ function commitDetachRef(current: Fiber) {
1375
const currentRef = current.ref;
1376
if (currentRef !== null) {
1377
if (typeof currentRef === 'function') {
1387
- if (
1388
- enableProfilerTimer &&
1389
- enableProfilerCommitHooks &&
1390
- current.mode & ProfileMode
1391
- ) {
1378
+ if (shouldProfile(current)) {
1379
try {
1380
startLayoutEffectTimer();
1381
currentRef(null);
@@ -1914,11 +1901,7 @@ function commitDeletionEffectsOnFiber(
1901
markComponentLayoutEffectUnmountStarted(deletedFiber);
1902
}
1903
1917
- if (
1918
- enableProfilerTimer &&
1919
- enableProfilerCommitHooks &&
1920
- deletedFiber.mode & ProfileMode
1921
- ) {
1904
+ if (shouldProfile(deletedFiber)) {
1905
startLayoutEffectTimer();
1906
safelyCallDestroy(
1907
deletedFiber,
@@ -2237,11 +2220,7 @@ function commitMutationEffectsOnFiber(
2220
// This prevents sibling component effects from interfering with each other,
2221
// e.g. a destroy function in one component should never override a ref set
2222
// by a create function in another component during the same commit.
2240
- if (
2241
- enableProfilerTimer &&
2242
- enableProfilerCommitHooks &&
2243
- finishedWork.mode & ProfileMode
2244
- ) {
2223
+ if (shouldProfile(finishedWork)) {
2224
try {
2225
startLayoutEffectTimer();
2226
commitHookEffectListUnmount(
@@ -2627,18 +2606,14 @@ function recursivelyTraverseLayoutEffects(
2606
setCurrentDebugFiberInDEV(prevDebugFiber);
2607
}
2608
2630
-function disappearLayoutEffects(finishedWork: Fiber) {
2609
+export function disappearLayoutEffects(finishedWork: Fiber) {
2610
switch (finishedWork.tag) {
2611
case FunctionComponent:
2612
case ForwardRef:
2613
case MemoComponent:
2614
case SimpleMemoComponent: {
2615
// TODO (Offscreen) Check: flags & LayoutStatic
2637
- if (
2638
- enableProfilerTimer &&
2639
- enableProfilerCommitHooks &&
2640
- finishedWork.mode & ProfileMode
2641
- ) {
2616
+ if (shouldProfile(finishedWork)) {
2617
try {
2618
startLayoutEffectTimer();
2619
commitHookEffectListUnmount(
@@ -2709,7 +2684,7 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2684
}
2685
}
2686
2712
-function reappearLayoutEffects(
2687
+export function reappearLayoutEffects(
2688
finishedRoot: FiberRoot,
2689
current: Fiber | null,
2690
finishedWork: Fiber,
@@ -2876,11 +2851,7 @@ function commitHookPassiveMountEffects(
2851
finishedWork: Fiber,
2852
hookFlags: HookFlags,
2853
) {
2879
- if (
2880
- enableProfilerTimer &&
2881
- enableProfilerCommitHooks &&
2882
- finishedWork.mode & ProfileMode
2883
- ) {
2854
+ if (shouldProfile(finishedWork)) {
2855
startPassiveEffectTimer();
2856
try {
2857
commitHookEffectListMount(hookFlags, finishedWork);
@@ -3304,7 +3275,7 @@ function recursivelyTraverseReconnectPassiveEffects(
3275
setCurrentDebugFiberInDEV(prevDebugFiber);
3276
}
3277
3307
-function reconnectPassiveEffects(
3278
+export function reconnectPassiveEffects(
3279
finishedRoot: FiberRoot,
3280
finishedWork: Fiber,
3281
committedLanes: Lanes,
@@ -3585,11 +3556,7 @@ function commitHookPassiveUnmountEffects(
3556
nearestMountedAncestor,
3557
hookFlags: HookFlags,
3558
) {
3588
- if (
3589
- enableProfilerTimer &&
3590
- enableProfilerCommitHooks &&
3591
- finishedWork.mode & ProfileMode
3592
- ) {
3559
+ if (shouldProfile(finishedWork)) {
3560
startPassiveEffectTimer();
3561
commitHookEffectListUnmount(
3562
hookFlags,
@@ -3718,7 +3685,7 @@ function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
3685
setCurrentDebugFiberInDEV(prevDebugFiber);
3686
}
3687
3721
-function disconnectPassiveEffect(finishedWork: Fiber): void {
3688
+export function disconnectPassiveEffect(finishedWork: Fiber): void {
3689
switch (finishedWork.tag) {
3690
case FunctionComponent:
3691
case ForwardRef:
@@ -3870,112 +3837,4 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3837
}
3838
}
3839
3873
-// TODO: Reuse reappearLayoutEffects traversal here?
3874
-function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3875
- if (__DEV__ && enableStrictEffects) {
3876
- // We don't need to re-check StrictEffectsMode here.
3877
- // This function is only called if that check has already passed.
3878
- switch (fiber.tag) {
3879
- case FunctionComponent:
3880
- case ForwardRef:
3881
- case SimpleMemoComponent: {
3882
- try {
3883
- commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
3884
- } catch (error) {
3885
- captureCommitPhaseError(fiber, fiber.return, error);
3886
- }
3887
- break;
3888
- }
3889
- case ClassComponent: {
3890
- const instance = fiber.stateNode;
3891
- try {
3892
- instance.componentDidMount();
3893
- } catch (error) {
3894
- captureCommitPhaseError(fiber, fiber.return, error);
3895
- }
3896
- break;
3897
- }
3898
- }
3899
- }
3900
-}
3901
-
3902
-function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3903
- if (__DEV__ && enableStrictEffects) {
3904
- // We don't need to re-check StrictEffectsMode here.
3905
- // This function is only called if that check has already passed.
3906
- switch (fiber.tag) {
3907
- case FunctionComponent:
3908
- case ForwardRef:
3909
- case SimpleMemoComponent: {
3910
- try {
3911
- commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
3912
- } catch (error) {
3913
- captureCommitPhaseError(fiber, fiber.return, error);
3914
- }
3915
- break;
3916
- }
3917
- }
3918
- }
3919
-}
3920
-
3921
-function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3922
- if (__DEV__ && enableStrictEffects) {
3923
- // We don't need to re-check StrictEffectsMode here.
3924
- // This function is only called if that check has already passed.
3925
- switch (fiber.tag) {
3926
- case FunctionComponent:
3927
- case ForwardRef:
3928
- case SimpleMemoComponent: {
3929
- try {
3930
- commitHookEffectListUnmount(
3931
- HookLayout | HookHasEffect,
3932
- fiber,
3933
- fiber.return,
3934
- );
3935
- } catch (error) {
3936
- captureCommitPhaseError(fiber, fiber.return, error);
3937
- }
3938
- break;
3939
- }
3940
- case ClassComponent: {
3941
- const instance = fiber.stateNode;
3942
- if (typeof instance.componentWillUnmount === 'function') {
3943
- safelyCallComponentWillUnmount(fiber, fiber.return, instance);
3944
- }
3945
- break;
3946
- }
3947
- }
3948
- }
3949
-}
3950
-
3951
-function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3952
- if (__DEV__ && enableStrictEffects) {
3953
- // We don't need to re-check StrictEffectsMode here.
3954
- // This function is only called if that check has already passed.
3955
- switch (fiber.tag) {
3956
- case FunctionComponent:
3957
- case ForwardRef:
3958
- case SimpleMemoComponent: {
3959
- try {
3960
- commitHookEffectListUnmount(
3961
- HookPassive | HookHasEffect,
3962
- fiber,
3963
- fiber.return,
3964
- );
3965
- } catch (error) {
3966
- captureCommitPhaseError(fiber, fiber.return, error);
3967
- }
3968
- }
3969
- }
3970
- }
3971
-}
3972
-
3973
-export {
3974
- commitPlacement,
3975
- commitAttachRef,
3976
- commitDetachRef,
3977
- invokeLayoutEffectMountInDEV,
3978
- invokeLayoutEffectUnmountInDEV,
3979
- invokePassiveEffectMountInDEV,
3980
- invokePassiveEffectUnmountInDEV,
3981
-};
3840
+export {commitPlacement, commitAttachRef, commitDetachRef};
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+35
-176
@@ -40,7 +40,6 @@ import {
40
enableSchedulingProfiler,
41
enableSuspenseCallback,
42
enableScopeAPI,
43
- enableStrictEffects,
43
deletedTreeCleanUpLevel,
44
enableUpdaterTracking,
45
enableCache,
@@ -148,6 +147,9 @@ import {
147
addMarkerProgressCallbackToPendingTransition,
148
addMarkerCompleteCallbackToPendingTransition,
149
setIsRunningInsertionEffect,
150
+ getExecutionContext,
151
+ CommitContext,
152
+ NoContext,
153
} from './ReactFiberWorkLoop.old';
154
import {
155
NoFlags as NoHookEffect,
@@ -200,6 +202,15 @@ let nextEffect: Fiber | null = null;
202
let inProgressLanes: Lanes | null = null;
203
let inProgressRoot: FiberRoot | null = null;
204
205
+function shouldProfile(current: Fiber): boolean {
206
+ return (
207
+ enableProfilerTimer &&
208
+ enableProfilerCommitHooks &&
209
+ (current.mode & ProfileMode) !== NoMode &&
210
+ (getExecutionContext() & CommitContext) !== NoContext
211
+ );
212
+}
213
+
214
export function reportUncaughtErrorInDEV(error: mixed) {
215
// Wrapping each small part of the commit phase into a guarded
216
// callback is a bit too slow (https://github.com/facebook/react/pull/21666).
@@ -217,11 +228,7 @@ export function reportUncaughtErrorInDEV(error: mixed) {
228
const callComponentWillUnmountWithTimer = function(current, instance) {
229
instance.props = current.memoizedProps;
230
instance.state = current.memoizedState;
220
- if (
221
- enableProfilerTimer &&
222
- enableProfilerCommitHooks &&
223
- current.mode & ProfileMode
224
- ) {
231
+ if (shouldProfile(current)) {
232
try {
233
startLayoutEffectTimer();
234
instance.componentWillUnmount();
@@ -261,11 +268,7 @@ function safelyDetachRef(current: Fiber, nearestMountedAncestor: Fiber | null) {
268
if (typeof ref === 'function') {
269
let retVal;
270
try {
264
- if (
265
- enableProfilerTimer &&
266
- enableProfilerCommitHooks &&
267
- current.mode & ProfileMode
268
- ) {
271
+ if (shouldProfile(current)) {
272
try {
273
startLayoutEffectTimer();
274
retVal = ref(null);
@@ -641,7 +644,11 @@ export function commitPassiveEffectDurations(
644
finishedRoot: FiberRoot,
645
finishedWork: Fiber,
646
): void {
644
- if (enableProfilerTimer && enableProfilerCommitHooks) {
647
+ if (
648
+ enableProfilerTimer &&
649
+ enableProfilerCommitHooks &&
650
+ getExecutionContext() & CommitContext
651
+ ) {
652
// Only Profilers with work in their subtree will have an Update effect scheduled.
653
if ((finishedWork.flags & Update) !== NoFlags) {
654
switch (finishedWork.tag) {
@@ -694,11 +701,7 @@ function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
701
// This is done to prevent sibling component effects from interfering with each other,
702
// e.g. a destroy function in one component should never override a ref set
703
// by a create function in another component during the same commit.
697
- if (
698
- enableProfilerTimer &&
699
- enableProfilerCommitHooks &&
700
- finishedWork.mode & ProfileMode
701
- ) {
704
+ if (shouldProfile(finishedWork)) {
705
try {
706
startLayoutEffectTimer();
707
commitHookEffectListMount(hookFlags, finishedWork);
@@ -751,11 +754,7 @@ function commitClassLayoutLifecycles(
754
}
755
}
756
}
754
- if (
755
- enableProfilerTimer &&
756
- enableProfilerCommitHooks &&
757
- finishedWork.mode & ProfileMode
758
- ) {
757
+ if (shouldProfile(finishedWork)) {
758
try {
759
startLayoutEffectTimer();
760
instance.componentDidMount();
@@ -806,11 +805,7 @@ function commitClassLayoutLifecycles(
805
}
806
}
807
}
809
- if (
810
- enableProfilerTimer &&
811
- enableProfilerCommitHooks &&
812
- finishedWork.mode & ProfileMode
813
- ) {
808
+ if (shouldProfile(finishedWork)) {
809
try {
810
startLayoutEffectTimer();
811
instance.componentDidUpdate(
@@ -892,7 +887,7 @@ function commitHostComponentMount(finishedWork: Fiber) {
887
}
888
889
function commitProfilerUpdate(finishedWork: Fiber, current: Fiber | null) {
895
- if (enableProfilerTimer) {
890
+ if (enableProfilerTimer && getExecutionContext() & CommitContext) {
891
try {
892
const {onCommit, onRender} = finishedWork.memoizedProps;
893
const {effectDuration} = finishedWork.stateNode;
@@ -1341,11 +1336,7 @@ function commitAttachRef(finishedWork: Fiber) {
1336
}
1337
if (typeof ref === 'function') {
1338
let retVal;
1344
- if (
1345
- enableProfilerTimer &&
1346
- enableProfilerCommitHooks &&
1347
- finishedWork.mode & ProfileMode
1348
- ) {
1339
+ if (shouldProfile(finishedWork)) {
1340
try {
1341
startLayoutEffectTimer();
1342
retVal = ref(instanceToUse);
@@ -1384,11 +1375,7 @@ function commitDetachRef(current: Fiber) {
1375
const currentRef = current.ref;
1376
if (currentRef !== null) {
1377
if (typeof currentRef === 'function') {
1387
- if (
1388
- enableProfilerTimer &&
1389
- enableProfilerCommitHooks &&
1390
- current.mode & ProfileMode
1391
- ) {
1378
+ if (shouldProfile(current)) {
1379
try {
1380
startLayoutEffectTimer();
1381
currentRef(null);
@@ -1914,11 +1901,7 @@ function commitDeletionEffectsOnFiber(
1901
markComponentLayoutEffectUnmountStarted(deletedFiber);
1902
}
1903
1917
- if (
1918
- enableProfilerTimer &&
1919
- enableProfilerCommitHooks &&
1920
- deletedFiber.mode & ProfileMode
1921
- ) {
1904
+ if (shouldProfile(deletedFiber)) {
1905
startLayoutEffectTimer();
1906
safelyCallDestroy(
1907
deletedFiber,
@@ -2237,11 +2220,7 @@ function commitMutationEffectsOnFiber(
2220
// This prevents sibling component effects from interfering with each other,
2221
// e.g. a destroy function in one component should never override a ref set
2222
// by a create function in another component during the same commit.
2240
- if (
2241
- enableProfilerTimer &&
2242
- enableProfilerCommitHooks &&
2243
- finishedWork.mode & ProfileMode
2244
- ) {
2223
+ if (shouldProfile(finishedWork)) {
2224
try {
2225
startLayoutEffectTimer();
2226
commitHookEffectListUnmount(
@@ -2627,18 +2606,14 @@ function recursivelyTraverseLayoutEffects(
2606
setCurrentDebugFiberInDEV(prevDebugFiber);
2607
}
2608
2630
-function disappearLayoutEffects(finishedWork: Fiber) {
2609
+export function disappearLayoutEffects(finishedWork: Fiber) {
2610
switch (finishedWork.tag) {
2611
case FunctionComponent:
2612
case ForwardRef:
2613
case MemoComponent:
2614
case SimpleMemoComponent: {
2615
// TODO (Offscreen) Check: flags & LayoutStatic
2637
- if (
2638
- enableProfilerTimer &&
2639
- enableProfilerCommitHooks &&
2640
- finishedWork.mode & ProfileMode
2641
- ) {
2616
+ if (shouldProfile(finishedWork)) {
2617
try {
2618
startLayoutEffectTimer();
2619
commitHookEffectListUnmount(
@@ -2709,7 +2684,7 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
2684
}
2685
}
2686
2712
-function reappearLayoutEffects(
2687
+export function reappearLayoutEffects(
2688
finishedRoot: FiberRoot,
2689
current: Fiber | null,
2690
finishedWork: Fiber,
@@ -2876,11 +2851,7 @@ function commitHookPassiveMountEffects(
2851
finishedWork: Fiber,
2852
hookFlags: HookFlags,
2853
) {
2879
- if (
2880
- enableProfilerTimer &&
2881
- enableProfilerCommitHooks &&
2882
- finishedWork.mode & ProfileMode
2883
- ) {
2854
+ if (shouldProfile(finishedWork)) {
2855
startPassiveEffectTimer();
2856
try {
2857
commitHookEffectListMount(hookFlags, finishedWork);
@@ -3304,7 +3275,7 @@ function recursivelyTraverseReconnectPassiveEffects(
3275
setCurrentDebugFiberInDEV(prevDebugFiber);
3276
}
3277
3307
-function reconnectPassiveEffects(
3278
+export function reconnectPassiveEffects(
3279
finishedRoot: FiberRoot,
3280
finishedWork: Fiber,
3281
committedLanes: Lanes,
@@ -3585,11 +3556,7 @@ function commitHookPassiveUnmountEffects(
3556
nearestMountedAncestor,
3557
hookFlags: HookFlags,
3558
) {
3588
- if (
3589
- enableProfilerTimer &&
3590
- enableProfilerCommitHooks &&
3591
- finishedWork.mode & ProfileMode
3592
- ) {
3559
+ if (shouldProfile(finishedWork)) {
3560
startPassiveEffectTimer();
3561
commitHookEffectListUnmount(
3562
hookFlags,
@@ -3718,7 +3685,7 @@ function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
3685
setCurrentDebugFiberInDEV(prevDebugFiber);
3686
}
3687
3721
-function disconnectPassiveEffect(finishedWork: Fiber): void {
3688
+export function disconnectPassiveEffect(finishedWork: Fiber): void {
3689
switch (finishedWork.tag) {
3690
case FunctionComponent:
3691
case ForwardRef:
@@ -3870,112 +3837,4 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3837
}
3838
}
3839
3873
-// TODO: Reuse reappearLayoutEffects traversal here?
3874
-function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
3875
- if (__DEV__ && enableStrictEffects) {
3876
- // We don't need to re-check StrictEffectsMode here.
3877
- // This function is only called if that check has already passed.
3878
- switch (fiber.tag) {
3879
- case FunctionComponent:
3880
- case ForwardRef:
3881
- case SimpleMemoComponent: {
3882
- try {
3883
- commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
3884
- } catch (error) {
3885
- captureCommitPhaseError(fiber, fiber.return, error);
3886
- }
3887
- break;
3888
- }
3889
- case ClassComponent: {
3890
- const instance = fiber.stateNode;
3891
- try {
3892
- instance.componentDidMount();
3893
- } catch (error) {
3894
- captureCommitPhaseError(fiber, fiber.return, error);
3895
- }
3896
- break;
3897
- }
3898
- }
3899
- }
3900
-}
3901
-
3902
-function invokePassiveEffectMountInDEV(fiber: Fiber): void {
3903
- if (__DEV__ && enableStrictEffects) {
3904
- // We don't need to re-check StrictEffectsMode here.
3905
- // This function is only called if that check has already passed.
3906
- switch (fiber.tag) {
3907
- case FunctionComponent:
3908
- case ForwardRef:
3909
- case SimpleMemoComponent: {
3910
- try {
3911
- commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
3912
- } catch (error) {
3913
- captureCommitPhaseError(fiber, fiber.return, error);
3914
- }
3915
- break;
3916
- }
3917
- }
3918
- }
3919
-}
3920
-
3921
-function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
3922
- if (__DEV__ && enableStrictEffects) {
3923
- // We don't need to re-check StrictEffectsMode here.
3924
- // This function is only called if that check has already passed.
3925
- switch (fiber.tag) {
3926
- case FunctionComponent:
3927
- case ForwardRef:
3928
- case SimpleMemoComponent: {
3929
- try {
3930
- commitHookEffectListUnmount(
3931
- HookLayout | HookHasEffect,
3932
- fiber,
3933
- fiber.return,
3934
- );
3935
- } catch (error) {
3936
- captureCommitPhaseError(fiber, fiber.return, error);
3937
- }
3938
- break;
3939
- }
3940
- case ClassComponent: {
3941
- const instance = fiber.stateNode;
3942
- if (typeof instance.componentWillUnmount === 'function') {
3943
- safelyCallComponentWillUnmount(fiber, fiber.return, instance);
3944
- }
3945
- break;
3946
- }
3947
- }
3948
- }
3949
-}
3950
-
3951
-function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
3952
- if (__DEV__ && enableStrictEffects) {
3953
- // We don't need to re-check StrictEffectsMode here.
3954
- // This function is only called if that check has already passed.
3955
- switch (fiber.tag) {
3956
- case FunctionComponent:
3957
- case ForwardRef:
3958
- case SimpleMemoComponent: {
3959
- try {
3960
- commitHookEffectListUnmount(
3961
- HookPassive | HookHasEffect,
3962
- fiber,
3963
- fiber.return,
3964
- );
3965
- } catch (error) {
3966
- captureCommitPhaseError(fiber, fiber.return, error);
3967
- }
3968
- }
3969
- }
3970
- }
3971
-}
3972
-
3973
-export {
3974
- commitPlacement,
3975
- commitAttachRef,
3976
- commitDetachRef,
3977
- invokeLayoutEffectMountInDEV,
3978
- invokeLayoutEffectUnmountInDEV,
3979
- invokePassiveEffectMountInDEV,
3980
- invokePassiveEffectUnmountInDEV,
3981
-};
3840
+export {commitPlacement, commitAttachRef, commitDetachRef};
packages/react-reconciler/src/ReactFiberFlags.js
+28
-27
@@ -12,52 +12,53 @@ import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
12
export type Flags = number;
13
14
// Don't change these two values. They're used by React Dev Tools.
15
-export const NoFlags = /* */ 0b0000000000000000000000000;
16
-export const PerformedWork = /* */ 0b0000000000000000000000001;
15
+export const NoFlags = /* */ 0b00000000000000000000000000;
16
+export const PerformedWork = /* */ 0b00000000000000000000000001;
17
18
// You can change the rest (and add more).
19
-export const Placement = /* */ 0b0000000000000000000000010;
20
-export const Update = /* */ 0b0000000000000000000000100;
21
-export const ChildDeletion = /* */ 0b0000000000000000000001000;
22
-export const ContentReset = /* */ 0b0000000000000000000010000;
23
-export const Callback = /* */ 0b0000000000000000000100000;
24
-export const DidCapture = /* */ 0b0000000000000000001000000;
25
-export const ForceClientRender = /* */ 0b0000000000000000010000000;
26
-export const Ref = /* */ 0b0000000000000000100000000;
27
-export const Snapshot = /* */ 0b0000000000000001000000000;
28
-export const Passive = /* */ 0b0000000000000010000000000;
29
-export const Hydrating = /* */ 0b0000000000000100000000000;
30
-export const Visibility = /* */ 0b0000000000001000000000000;
31
-export const StoreConsistency = /* */ 0b0000000000010000000000000;
19
+export const Placement = /* */ 0b00000000000000000000000010;
20
+export const Update = /* */ 0b00000000000000000000000100;
21
+export const ChildDeletion = /* */ 0b00000000000000000000001000;
22
+export const ContentReset = /* */ 0b00000000000000000000010000;
23
+export const Callback = /* */ 0b00000000000000000000100000;
24
+export const DidCapture = /* */ 0b00000000000000000001000000;
25
+export const ForceClientRender = /* */ 0b00000000000000000010000000;
26
+export const Ref = /* */ 0b00000000000000000100000000;
27
+export const Snapshot = /* */ 0b00000000000000001000000000;
28
+export const Passive = /* */ 0b00000000000000010000000000;
29
+export const Hydrating = /* */ 0b00000000000000100000000000;
30
+export const Visibility = /* */ 0b00000000000001000000000000;
31
+export const StoreConsistency = /* */ 0b00000000000010000000000000;
32
33
export const LifecycleEffectMask =
34
Passive | Update | Callback | Ref | Snapshot | StoreConsistency;
35
36
// Union of all commit flags (flags with the lifetime of a particular commit)
37
-export const HostEffectMask = /* */ 0b0000000000011111111111111;
37
+export const HostEffectMask = /* */ 0b00000000000011111111111111;
38
39
// These are not really side effects, but we still reuse this field.
40
-export const Incomplete = /* */ 0b0000000000100000000000000;
41
-export const ShouldCapture = /* */ 0b0000000001000000000000000;
42
-export const ForceUpdateForLegacySuspense = /* */ 0b0000000010000000000000000;
43
-export const DidPropagateContext = /* */ 0b0000000100000000000000000;
44
-export const NeedsPropagation = /* */ 0b0000001000000000000000000;
45
-export const Forked = /* */ 0b0000010000000000000000000;
40
+export const Incomplete = /* */ 0b00000000000100000000000000;
41
+export const ShouldCapture = /* */ 0b00000000001000000000000000;
42
+export const ForceUpdateForLegacySuspense = /* */ 0b00000000010000000000000000;
43
+export const DidPropagateContext = /* */ 0b00000000100000000000000000;
44
+export const NeedsPropagation = /* */ 0b00000001000000000000000000;
45
+export const Forked = /* */ 0b00000010000000000000000000;
46
47
// Static tags describe aspects of a fiber that are not specific to a render,
48
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
49
// This enables us to defer more work in the unmount case,
50
// since we can defer traversing the tree during layout to look for Passive effects,
51
// and instead rely on the static flag as a signal that there may be cleanup work.
52
-export const RefStatic = /* */ 0b0000100000000000000000000;
53
-export const LayoutStatic = /* */ 0b0001000000000000000000000;
54
-export const PassiveStatic = /* */ 0b0010000000000000000000000;
52
+export const RefStatic = /* */ 0b00000100000000000000000000;
53
+export const LayoutStatic = /* */ 0b00001000000000000000000000;
54
+export const PassiveStatic = /* */ 0b00010000000000000000000000;
55
56
// These flags allow us to traverse to fibers that have effects on mount
57
// without traversing the entire tree after every commit for
58
// double invoking
59
-export const MountLayoutDev = /* */ 0b0100000000000000000000000;
60
-export const MountPassiveDev = /* */ 0b1000000000000000000000000;
59
+export const MountLayoutDev = /* */ 0b00100000000000000000000000;
60
+export const MountPassiveDev = /* */ 0b01000000000000000000000000;
61
+export const PlacementDEV = /* */ 0b10000000000000000000000000;
62
63
// Groups of flags that are used in the commit phase to skip over trees that
64
// don't contain effects, by checking subtreeFlags.
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+58
-59
@@ -7,11 +7,12 @@
7
* @flow
8
*/
9
10
+import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11
+
12
import type {Wakeable} from 'shared/ReactTypes';
13
import type {Fiber, FiberRoot} from './ReactInternalTypes';
14
import type {Lanes, Lane} from './ReactFiberLane.new';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14
-import type {Flags} from './ReactFiberFlags';
16
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
17
import type {EventPriority} from './ReactEventPriorities.new';
18
import type {
@@ -90,7 +91,13 @@ import {
91
} from './ReactFiber.new';
92
import {isRootDehydrated} from './ReactFiberShellHydration';
93
import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.new';
93
-import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
94
+import {
95
+ NoMode,
96
+ ProfileMode,
97
+ ConcurrentMode,
98
+ StrictLegacyMode,
99
+ StrictEffectsMode,
100
+} from './ReactTypeOfMode';
101
import {
102
HostRoot,
103
IndeterminateComponent,
@@ -104,7 +111,7 @@ import {
111
SimpleMemoComponent,
112
Profiler,
113
} from './ReactWorkTags';
107
-import {LegacyRoot} from './ReactRootTags';
114
+import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
115
import {
116
NoFlags,
117
Incomplete,
@@ -115,8 +122,7 @@ import {
122
MutationMask,
123
LayoutMask,
124
PassiveMask,
118
- MountPassiveDev,
119
- MountLayoutDev,
125
+ PlacementDEV,
126
} from './ReactFiberFlags';
127
import {
128
NoLanes,
@@ -176,10 +182,10 @@ import {
182
commitPassiveEffectDurations,
183
commitPassiveMountEffects,
184
commitPassiveUnmountEffects,
179
- invokeLayoutEffectMountInDEV,
180
- invokePassiveEffectMountInDEV,
181
- invokeLayoutEffectUnmountInDEV,
182
- invokePassiveEffectUnmountInDEV,
185
+ disappearLayoutEffects,
186
+ reconnectPassiveEffects,
187
+ reappearLayoutEffects,
188
+ disconnectPassiveEffect,
189
reportUncaughtErrorInDEV,
190
} from './ReactFiberCommitWork.new';
191
import {enqueueUpdate} from './ReactFiberClassUpdateQueue.new';
@@ -267,7 +273,7 @@ type ExecutionContext = number;
273
export const NoContext = /* */ 0b000;
274
const BatchedContext = /* */ 0b001;
275
const RenderContext = /* */ 0b010;
270
-const CommitContext = /* */ 0b100;
276
+export const CommitContext = /* */ 0b100;
277
278
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
279
const RootInProgress = 0;
@@ -2422,7 +2428,7 @@ function commitRootImpl(
2428
2429
if (__DEV__ && enableStrictEffects) {
2430
if (!rootDidHavePassiveEffects) {
2425
- commitDoubleInvokeEffectsInDEV(root.current, false);
2431
+ commitDoubleInvokeEffectsInDEV(root);
2432
}
2433
}
2434
@@ -2639,7 +2645,7 @@ function flushPassiveEffectsImpl() {
2645
}
2646
2647
if (__DEV__ && enableStrictEffects) {
2642
- commitDoubleInvokeEffectsInDEV(root.current, true);
2648
+ commitDoubleInvokeEffectsInDEV(root);
2649
}
2650
2651
executionContext = prevExecutionContext;
@@ -2993,64 +2999,57 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2999
}
3000
}
3001
2996
-function commitDoubleInvokeEffectsInDEV(
2997
- fiber: Fiber,
2998
- hasPassiveEffects: boolean,
3002
+function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3003
+ root: FiberRoot,
3004
+ parentFiber: Fiber,
3005
+ isInStrictMode: boolean,
3006
) {
3000
- if (__DEV__ && enableStrictEffects) {
3001
- // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3002
- // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3003
- // Maybe not a big deal since this is DEV only behavior.
3007
+ let child = parentFiber.child;
3008
+ while (child !== null) {
3009
+ doubleInvokeEffectsInDEV(root, child, isInStrictMode);
3010
+ child = child.sibling;
3011
+ }
3012
+}
3013
3014
+function doubleInvokeEffectsInDEV(
3015
+ root: FiberRoot,
3016
+ fiber: Fiber,
3017
+ parentIsInStrictMode: boolean,
3018
+) {
3019
+ const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
3020
+ const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3021
+ if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
3022
setCurrentDebugFiberInDEV(fiber);
3006
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3007
- if (hasPassiveEffects) {
3008
- invokeEffectsInDev(
3009
- fiber,
3010
- MountPassiveDev,
3011
- invokePassiveEffectUnmountInDEV,
3012
- );
3013
- }
3014
-
3015
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3016
- if (hasPassiveEffects) {
3017
- invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3023
+ if (isInStrictMode) {
3024
+ disappearLayoutEffects(fiber);
3025
+ disconnectPassiveEffect(fiber);
3026
+ reappearLayoutEffects(root, fiber.alternate, fiber, false);
3027
+ reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3028
}
3029
resetCurrentDebugFiberInDEV();
3030
+ } else {
3031
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
3032
}
3033
}
3034
3023
-function invokeEffectsInDev(
3024
- firstChild: Fiber,
3025
- fiberFlags: Flags,
3026
- invokeEffectFn: (fiber: Fiber) => void,
3027
-): void {
3035
+function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3036
if (__DEV__ && enableStrictEffects) {
3029
- // We don't need to re-check StrictEffectsMode here.
3030
- // This function is only called if that check has already passed.
3037
+ let doubleInvokeEffects = true;
3038
3032
- let current = firstChild;
3033
- let subtreeRoot = null;
3034
- while (current !== null) {
3035
- const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3036
- if (
3037
- current !== subtreeRoot &&
3038
- current.child !== null &&
3039
- primarySubtreeFlag !== NoFlags
3040
- ) {
3041
- current = current.child;
3042
- } else {
3043
- if ((current.flags & fiberFlags) !== NoFlags) {
3044
- invokeEffectFn(current);
3045
- }
3046
-
3047
- if (current.sibling !== null) {
3048
- current = current.sibling;
3049
- } else {
3050
- current = subtreeRoot = current.return;
3051
- }
3052
- }
3039
+ if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3040
+ doubleInvokeEffects = false;
3041
+ }
3042
+ if (
3043
+ root.tag === ConcurrentRoot &&
3044
+ !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3045
+ ) {
3046
+ doubleInvokeEffects = false;
3047
}
3048
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3049
+ root,
3050
+ root.current,
3051
+ doubleInvokeEffects,
3052
+ );
3053
}
3054
}
3055
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+58
-59
@@ -7,11 +7,12 @@
7
* @flow
8
*/
9
10
+import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11
+
12
import type {Wakeable} from 'shared/ReactTypes';
13
import type {Fiber, FiberRoot} from './ReactInternalTypes';
14
import type {Lanes, Lane} from './ReactFiberLane.old';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
14
-import type {Flags} from './ReactFiberFlags';
16
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
17
import type {EventPriority} from './ReactEventPriorities.old';
18
import type {
@@ -90,7 +91,13 @@ import {
91
} from './ReactFiber.old';
92
import {isRootDehydrated} from './ReactFiberShellHydration';
93
import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.old';
93
-import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
94
+import {
95
+ NoMode,
96
+ ProfileMode,
97
+ ConcurrentMode,
98
+ StrictLegacyMode,
99
+ StrictEffectsMode,
100
+} from './ReactTypeOfMode';
101
import {
102
HostRoot,
103
IndeterminateComponent,
@@ -104,7 +111,7 @@ import {
111
SimpleMemoComponent,
112
Profiler,
113
} from './ReactWorkTags';
107
-import {LegacyRoot} from './ReactRootTags';
114
+import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
115
import {
116
NoFlags,
117
Incomplete,
@@ -115,8 +122,7 @@ import {
122
MutationMask,
123
LayoutMask,
124
PassiveMask,
118
- MountPassiveDev,
119
- MountLayoutDev,
125
+ PlacementDEV,
126
} from './ReactFiberFlags';
127
import {
128
NoLanes,
@@ -176,10 +182,10 @@ import {
182
commitPassiveEffectDurations,
183
commitPassiveMountEffects,
184
commitPassiveUnmountEffects,
179
- invokeLayoutEffectMountInDEV,
180
- invokePassiveEffectMountInDEV,
181
- invokeLayoutEffectUnmountInDEV,
182
- invokePassiveEffectUnmountInDEV,
185
+ disappearLayoutEffects,
186
+ reconnectPassiveEffects,
187
+ reappearLayoutEffects,
188
+ disconnectPassiveEffect,
189
reportUncaughtErrorInDEV,
190
} from './ReactFiberCommitWork.old';
191
import {enqueueUpdate} from './ReactFiberClassUpdateQueue.old';
@@ -267,7 +273,7 @@ type ExecutionContext = number;
273
export const NoContext = /* */ 0b000;
274
const BatchedContext = /* */ 0b001;
275
const RenderContext = /* */ 0b010;
270
-const CommitContext = /* */ 0b100;
276
+export const CommitContext = /* */ 0b100;
277
278
type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
279
const RootInProgress = 0;
@@ -2422,7 +2428,7 @@ function commitRootImpl(
2428
2429
if (__DEV__ && enableStrictEffects) {
2430
if (!rootDidHavePassiveEffects) {
2425
- commitDoubleInvokeEffectsInDEV(root.current, false);
2431
+ commitDoubleInvokeEffectsInDEV(root);
2432
}
2433
}
2434
@@ -2639,7 +2645,7 @@ function flushPassiveEffectsImpl() {
2645
}
2646
2647
if (__DEV__ && enableStrictEffects) {
2642
- commitDoubleInvokeEffectsInDEV(root.current, true);
2648
+ commitDoubleInvokeEffectsInDEV(root);
2649
}
2650
2651
executionContext = prevExecutionContext;
@@ -2993,64 +2999,57 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2999
}
3000
}
3001
2996
-function commitDoubleInvokeEffectsInDEV(
2997
- fiber: Fiber,
2998
- hasPassiveEffects: boolean,
3002
+function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3003
+ root: FiberRoot,
3004
+ parentFiber: Fiber,
3005
+ isInStrictMode: boolean,
3006
) {
3000
- if (__DEV__ && enableStrictEffects) {
3001
- // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
3002
- // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
3003
- // Maybe not a big deal since this is DEV only behavior.
3007
+ let child = parentFiber.child;
3008
+ while (child !== null) {
3009
+ doubleInvokeEffectsInDEV(root, child, isInStrictMode);
3010
+ child = child.sibling;
3011
+ }
3012
+}
3013
3014
+function doubleInvokeEffectsInDEV(
3015
+ root: FiberRoot,
3016
+ fiber: Fiber,
3017
+ parentIsInStrictMode: boolean,
3018
+) {
3019
+ const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
3020
+ const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
3021
+ if (fiber.flags & PlacementDEV || fiber.tag === OffscreenComponent) {
3022
setCurrentDebugFiberInDEV(fiber);
3006
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3007
- if (hasPassiveEffects) {
3008
- invokeEffectsInDev(
3009
- fiber,
3010
- MountPassiveDev,
3011
- invokePassiveEffectUnmountInDEV,
3012
- );
3013
- }
3014
-
3015
- invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
3016
- if (hasPassiveEffects) {
3017
- invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
3023
+ if (isInStrictMode) {
3024
+ disappearLayoutEffects(fiber);
3025
+ disconnectPassiveEffect(fiber);
3026
+ reappearLayoutEffects(root, fiber.alternate, fiber, false);
3027
+ reconnectPassiveEffects(root, fiber, NoLanes, null, false);
3028
}
3029
resetCurrentDebugFiberInDEV();
3030
+ } else {
3031
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
3032
}
3033
}
3034
3023
-function invokeEffectsInDev(
3024
- firstChild: Fiber,
3025
- fiberFlags: Flags,
3026
- invokeEffectFn: (fiber: Fiber) => void,
3027
-): void {
3035
+function commitDoubleInvokeEffectsInDEV(root: FiberRoot) {
3036
if (__DEV__ && enableStrictEffects) {
3029
- // We don't need to re-check StrictEffectsMode here.
3030
- // This function is only called if that check has already passed.
3037
+ let doubleInvokeEffects = true;
3038
3032
- let current = firstChild;
3033
- let subtreeRoot = null;
3034
- while (current !== null) {
3035
- const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
3036
- if (
3037
- current !== subtreeRoot &&
3038
- current.child !== null &&
3039
- primarySubtreeFlag !== NoFlags
3040
- ) {
3041
- current = current.child;
3042
- } else {
3043
- if ((current.flags & fiberFlags) !== NoFlags) {
3044
- invokeEffectFn(current);
3045
- }
3046
-
3047
- if (current.sibling !== null) {
3048
- current = current.sibling;
3049
- } else {
3050
- current = subtreeRoot = current.return;
3051
- }
3052
- }
3039
+ if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) {
3040
+ doubleInvokeEffects = false;
3041
+ }
3042
+ if (
3043
+ root.tag === ConcurrentRoot &&
3044
+ !(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
3045
+ ) {
3046
+ doubleInvokeEffects = false;
3047
}
3048
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(
3049
+ root,
3050
+ root.current,
3051
+ doubleInvokeEffects,
3052
+ );
3053
}
3054
}
3055
packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
new
+95
@@ -0,0 +1,95 @@
1
+let React;
2
+let Offscreen;
3
+let ReactNoop;
4
+let act;
5
+let log;
6
+
7
+describe('ReactOffscreenStrictMode', () => {
8
+ beforeEach(() => {
9
+ jest.resetModules();
10
+ log = [];
11
+
12
+ React = require('react');
13
+ Offscreen = React.unstable_Offscreen;
14
+ ReactNoop = require('react-noop-renderer');
15
+ act = require('jest-react').act;
16
+ });
17
+
18
+ function Component({label}) {
19
+ React.useEffect(() => {
20
+ log.push(`${label}: useEffect mount`);
21
+ return () => log.push(`${label}: useEffect unmount`);
22
+ });
23
+
24
+ React.useLayoutEffect(() => {
25
+ log.push(`${label}: useLayoutEffect mount`);
26
+ return () => log.push(`${label}: useLayoutEffect unmount`);
27
+ });
28
+
29
+ log.push(`${label}: render`);
30
+
31
+ return <span>label</span>;
32
+ }
33
+
34
+ // @gate __DEV__ && enableStrictEffects && enableOffscreen
35
+ it('should trigger strict effects when offscreen is visible', () => {
36
+ act(() => {
37
+ ReactNoop.render(
38
+ <React.StrictMode>
39
+ <Offscreen mode="visible">
40
+ <Component label="A" />
41
+ </Offscreen>
42
+ </React.StrictMode>,
43
+ );
44
+ });
45
+
46
+ expect(log).toEqual([
47
+ 'A: render',
48
+ 'A: render',
49
+ 'A: useLayoutEffect mount',
50
+ 'A: useEffect mount',
51
+ 'A: useLayoutEffect unmount',
52
+ 'A: useEffect unmount',
53
+ 'A: useLayoutEffect mount',
54
+ 'A: useEffect mount',
55
+ ]);
56
+ });
57
+
58
+ // @gate __DEV__ && enableStrictEffects && enableOffscreen
59
+ it('should not trigger strict effects when offscreen is hidden', () => {
60
+ act(() => {
61
+ ReactNoop.render(
62
+ <React.StrictMode>
63
+ <Offscreen mode="hidden">
64
+ <Component label="A" />
65
+ </Offscreen>
66
+ </React.StrictMode>,
67
+ );
68
+ });
69
+
70
+ expect(log).toEqual(['A: render', 'A: render']);
71
+
72
+ log = [];
73
+
74
+ act(() => {
75
+ ReactNoop.render(
76
+ <React.StrictMode>
77
+ <Offscreen mode="visible">
78
+ <Component label="A" />
79
+ </Offscreen>
80
+ </React.StrictMode>,
81
+ );
82
+ });
83
+
84
+ expect(log).toEqual([
85
+ 'A: render',
86
+ 'A: render',
87
+ 'A: useLayoutEffect mount',
88
+ 'A: useEffect mount',
89
+ 'A: useLayoutEffect unmount',
90
+ 'A: useEffect unmount',
91
+ 'A: useLayoutEffect mount',
92
+ 'A: useEffect mount',
93
+ ]);
94
+ });
95
+});
packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
+24
-3
@@ -153,10 +153,8 @@ describe('StrictEffectsMode defaults', () => {
153
</>,
154
);
155
156
- expect(Scheduler).toFlushAndYieldThrough([
157
- 'useLayoutEffect mount "one"',
158
- ]);
156
expect(Scheduler).toFlushAndYield([
157
+ 'useLayoutEffect mount "one"',
158
'useEffect mount "one"',
159
'useLayoutEffect unmount "one"',
160
'useEffect unmount "one"',
@@ -381,6 +379,29 @@ describe('StrictEffectsMode defaults', () => {
379
expect(Scheduler).toHaveYielded([]);
380
});
381
382
+ it('disconnects refs during double invoking', () => {
383
+ const onRefMock = jest.fn();
384
+ function App({text}) {
385
+ return (
386
+ <span
387
+ ref={ref => {
388
+ onRefMock(ref);
389
+ }}>
390
+ text
391
+ </span>
392
+ );
393
+ }
394
+
395
+ act(() => {
396
+ ReactNoop.render(<App text={'mount'} />);
397
+ });
398
+
399
+ expect(onRefMock.mock.calls.length).toBe(3);
400
+ expect(onRefMock.mock.calls[0][0]).not.toBeNull();
401
+ expect(onRefMock.mock.calls[1][0]).toBe(null);
402
+ expect(onRefMock.mock.calls[2][0]).not.toBeNull();
403
+ });
404
+
405
it('passes the right context to class component lifecycles', () => {
406
class App extends React.PureComponent {
407
test() {}
packages/react/src/__tests__/ReactStrictMode-test.js
+25
-49
@@ -67,6 +67,7 @@ describe('ReactStrictMode', () => {
67
);
68
});
69
70
+ // @gate __DEV__ && !enableStrictEffects
71
it('should invoke precommit lifecycle methods twice', () => {
72
let log = [];
73
let shouldComponentUpdate = false;
@@ -107,24 +108,15 @@ describe('ReactStrictMode', () => {
108
container,
109
);
110
110
- if (__DEV__) {
111
- expect(log).toEqual([
112
- 'constructor',
113
- 'constructor',
114
- 'getDerivedStateFromProps',
115
- 'getDerivedStateFromProps',
116
- 'render',
117
- 'render',
118
- 'componentDidMount',
119
- ]);
120
- } else {
121
- expect(log).toEqual([
122
- 'constructor',
123
- 'getDerivedStateFromProps',
124
- 'render',
125
- 'componentDidMount',
126
- ]);
127
- }
111
+ expect(log).toEqual([
112
+ 'constructor',
113
+ 'constructor',
114
+ 'getDerivedStateFromProps',
115
+ 'getDerivedStateFromProps',
116
+ 'render',
117
+ 'render',
118
+ 'componentDidMount',
119
+ ]);
120
121
log = [];
122
shouldComponentUpdate = true;
@@ -135,24 +127,15 @@ describe('ReactStrictMode', () => {
127
</React.StrictMode>,
128
container,
129
);
138
- if (__DEV__) {
139
- expect(log).toEqual([
140
- 'getDerivedStateFromProps',
141
- 'getDerivedStateFromProps',
142
- 'shouldComponentUpdate',
143
- 'shouldComponentUpdate',
144
- 'render',
145
- 'render',
146
- 'componentDidUpdate',
147
- ]);
148
- } else {
149
- expect(log).toEqual([
150
- 'getDerivedStateFromProps',
151
- 'shouldComponentUpdate',
152
- 'render',
153
- 'componentDidUpdate',
154
- ]);
155
- }
130
+ expect(log).toEqual([
131
+ 'getDerivedStateFromProps',
132
+ 'getDerivedStateFromProps',
133
+ 'shouldComponentUpdate',
134
+ 'shouldComponentUpdate',
135
+ 'render',
136
+ 'render',
137
+ 'componentDidUpdate',
138
+ ]);
139
140
log = [];
141
shouldComponentUpdate = false;
@@ -164,19 +147,12 @@ describe('ReactStrictMode', () => {
147
container,
148
);
149
167
- if (__DEV__) {
168
- expect(log).toEqual([
169
- 'getDerivedStateFromProps',
170
- 'getDerivedStateFromProps',
171
- 'shouldComponentUpdate',
172
- 'shouldComponentUpdate',
173
- ]);
174
- } else {
175
- expect(log).toEqual([
176
- 'getDerivedStateFromProps',
177
- 'shouldComponentUpdate',
178
- ]);
179
- }
150
+ expect(log).toEqual([
151
+ 'getDerivedStateFromProps',
152
+ 'getDerivedStateFromProps',
153
+ 'shouldComponentUpdate',
154
+ 'shouldComponentUpdate',
155
+ ]);
156
});
157
158
it('should invoke setState callbacks twice', () => {