Skip to content

Commit c63741f

Browse files
authored
offscreen double invoke effects (#19523)
This PR double invokes effects in __DEV__ mode. We are thinking about unmounting layout and/or passive effects for a hidden tree. To understand potential issues with this, we want to double invoke effects. This PR changes the behavior in DEV when an effect runs from create() to create() -> destroy() -> create(). The effect cleanup function will still be called before the effect runs in both dev and prod. (Note: This change is purely for research for now as it is likely to break real code.) **Note: The change is fully behind a flag and does not affect any of the code on npm.**
1 parent a99bf5c commit c63741f

17 files changed

+888
-60
lines changed

packages/react-reconciler/src/ReactFiberClassComponent.new.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import type {Lanes} from './ReactFiberLane';
1212
importtype{UpdateQueue}from'./ReactUpdateQueue.new';
1313

1414
import*asReactfrom'react';
15-
import{Update,Snapshot}from'./ReactFiberFlags';
15+
import{Update,Snapshot,MountLayoutDev}from'./ReactFiberFlags';
1616
import{
1717
debugRenderPhaseSideEffectsForStrictMode,
1818
disableLegacyContext,
1919
enableDebugTracing,
2020
enableSchedulingProfiler,
2121
warnAboutDeprecatedLifecycles,
22+
enableDoubleInvokingEffects,
2223
}from'shared/ReactFeatureFlags';
2324
importReactStrictModeWarningsfrom'./ReactStrictModeWarnings.new';
2425
import{isMounted}from'./ReactFiberTreeReflection';
@@ -890,7 +891,11 @@ function mountClassInstance(
890891
}
891892

892893
if(typeofinstance.componentDidMount==='function'){
893-
workInProgress.flags|=Update;
894+
if(__DEV__&&enableDoubleInvokingEffects){
895+
workInProgress.flags|=MountLayoutDev|Update;
896+
}else{
897+
workInProgress.flags|=Update;
898+
}
894899
}
895900
}
896901

@@ -960,7 +965,11 @@ function resumeMountClassInstance(
960965
// If an update was already in progress, we should schedule an Update
961966
// effect even though we're bailing out, so that cWU/cDU are called.
962967
if(typeofinstance.componentDidMount==='function'){
963-
workInProgress.flags|=Update;
968+
if(__DEV__&&enableDoubleInvokingEffects){
969+
workInProgress.flags|=MountLayoutDev|Update;
970+
}else{
971+
workInProgress.flags|=Update;
972+
}
964973
}
965974
returnfalse;
966975
}
@@ -1003,13 +1012,21 @@ function resumeMountClassInstance(
10031012
}
10041013
}
10051014
if(typeofinstance.componentDidMount==='function'){
1006-
workInProgress.flags|=Update;
1015+
if(__DEV__&&enableDoubleInvokingEffects){
1016+
workInProgress.flags|=MountLayoutDev|Update;
1017+
}else{
1018+
workInProgress.flags|=Update;
1019+
}
10071020
}
10081021
}else{
10091022
// If an update was already in progress, we should schedule an Update
10101023
// effect even though we're bailing out, so that cWU/cDU are called.
10111024
if(typeofinstance.componentDidMount==='function'){
1012-
workInProgress.flags|=Update;
1025+
if(__DEV__&&enableDoubleInvokingEffects){
1026+
workInProgress.flags|=MountLayoutDev|Update;
1027+
}else{
1028+
workInProgress.flags|=Update;
1029+
}
10131030
}
10141031

10151032
// If shouldComponentUpdate returned false, we should still update the

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+135-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
enableFundamentalAPI,
3636
enableSuspenseCallback,
3737
enableScopeAPI,
38+
enableDoubleInvokingEffects,
3839
}from'shared/ReactFeatureFlags';
3940
import{
4041
FunctionComponent,
@@ -159,7 +160,7 @@ const callComponentWillUnmountWithTimer = function(current, instance) {
159160
functionsafelyCallComponentWillUnmount(
160161
current: Fiber,
161162
instance: any,
162-
nearestMountedAncestor: Fiber,
163+
nearestMountedAncestor: Fiber|null,
163164
){
164165
if(__DEV__){
165166
invokeGuardedCallback(
@@ -318,7 +319,7 @@ function commitBeforeMutationLifeCycles(
318319
}
319320

320321
functioncommitHookEffectListUnmount(
321-
tag: HookFlags,
322+
flags: HookFlags,
322323
finishedWork: Fiber,
323324
nearestMountedAncestor: Fiber|null,
324325
){
@@ -328,7 +329,7 @@ function commitHookEffectListUnmount(
328329
constfirstEffect=lastEffect.next;
329330
leteffect=firstEffect;
330331
do{
331-
if((effect.tag&tag)===tag){
332+
if((effect.tag&flags)===flags){
332333
// Unmount
333334
constdestroy=effect.destroy;
334335
effect.destroy=undefined;
@@ -341,14 +342,14 @@ function commitHookEffectListUnmount(
341342
}
342343
}
343344

344-
functioncommitHookEffectListMount(tag: HookFlags,finishedWork: Fiber){
345+
functioncommitHookEffectListMount(flags: HookFlags,finishedWork: Fiber){
345346
constupdateQueue: FunctionComponentUpdateQueue|null=(finishedWork.updateQueue: any);
346347
constlastEffect=updateQueue!==null ? updateQueue.lastEffect : null;
347348
if(lastEffect!==null){
348349
constfirstEffect=lastEffect.next;
349350
leteffect=firstEffect;
350351
do{
351-
if((effect.tag&tag)===tag){
352+
if((effect.tag&flags)===flags){
352353
// Mount
353354
constcreate=effect.create;
354355
effect.destroy=create();
@@ -1884,6 +1885,131 @@ function commitPassiveMount(
18841885
}
18851886
}
18861887

1888+
functioninvokeLayoutEffectMountInDEV(fiber: Fiber): void{
1889+
if(__DEV__&&enableDoubleInvokingEffects){
1890+
switch(fiber.tag){
1891+
caseFunctionComponent:
1892+
caseForwardRef:
1893+
caseSimpleMemoComponent:
1894+
caseBlock: {
1895+
invokeGuardedCallback(
1896+
null,
1897+
commitHookEffectListMount,
1898+
null,
1899+
HookLayout|HookHasEffect,
1900+
fiber,
1901+
);
1902+
if(hasCaughtError()){
1903+
constmountError=clearCaughtError();
1904+
captureCommitPhaseError(fiber,fiber.return,mountError);
1905+
}
1906+
break;
1907+
}
1908+
caseClassComponent: {
1909+
constinstance=fiber.stateNode;
1910+
invokeGuardedCallback(null,instance.componentDidMount,null);
1911+
if(hasCaughtError()){
1912+
constmountError=clearCaughtError();
1913+
captureCommitPhaseError(fiber,fiber.return,mountError);
1914+
}
1915+
break;
1916+
}
1917+
}
1918+
}
1919+
}
1920+
1921+
functioninvokePassiveEffectMountInDEV(fiber: Fiber): void{
1922+
if(__DEV__&&enableDoubleInvokingEffects){
1923+
switch(fiber.tag){
1924+
caseFunctionComponent:
1925+
caseForwardRef:
1926+
caseSimpleMemoComponent:
1927+
caseBlock: {
1928+
invokeGuardedCallback(
1929+
null,
1930+
commitHookEffectListMount,
1931+
null,
1932+
HookPassive|HookHasEffect,
1933+
fiber,
1934+
);
1935+
if(hasCaughtError()){
1936+
constmountError=clearCaughtError();
1937+
captureCommitPhaseError(fiber,fiber.return,mountError);
1938+
}
1939+
break;
1940+
}
1941+
}
1942+
}
1943+
}
1944+
1945+
functioninvokeLayoutEffectUnmountInDEV(fiber: Fiber): void{
1946+
if(__DEV__&&enableDoubleInvokingEffects){
1947+
switch(fiber.tag){
1948+
caseFunctionComponent:
1949+
caseForwardRef:
1950+
caseSimpleMemoComponent:
1951+
caseBlock: {
1952+
invokeGuardedCallback(
1953+
null,
1954+
commitHookEffectListUnmount,
1955+
null,
1956+
HookLayout|HookHasEffect,
1957+
fiber,
1958+
fiber.return,
1959+
);
1960+
if(hasCaughtError()){
1961+
constunmountError=clearCaughtError();
1962+
captureCommitPhaseError(fiber,fiber.return,unmountError);
1963+
}
1964+
break;
1965+
}
1966+
caseClassComponent: {
1967+
constinstance=fiber.stateNode;
1968+
if(typeofinstance.componentWillUnmount==='function'){
1969+
invokeGuardedCallback(
1970+
null,
1971+
safelyCallComponentWillUnmount,
1972+
null,
1973+
fiber,
1974+
instance,
1975+
fiber.return,
1976+
);
1977+
if(hasCaughtError()){
1978+
constunmountError=clearCaughtError();
1979+
captureCommitPhaseError(fiber,fiber.return,unmountError);
1980+
}
1981+
}
1982+
break;
1983+
}
1984+
}
1985+
}
1986+
}
1987+
1988+
functioninvokePassiveEffectUnmountInDEV(fiber: Fiber): void{
1989+
if(__DEV__&&enableDoubleInvokingEffects){
1990+
switch(fiber.tag){
1991+
caseFunctionComponent:
1992+
caseForwardRef:
1993+
caseSimpleMemoComponent:
1994+
caseBlock: {
1995+
invokeGuardedCallback(
1996+
null,
1997+
commitHookEffectListUnmount,
1998+
null,
1999+
HookPassive|HookHasEffect,
2000+
fiber,
2001+
fiber.return,
2002+
);
2003+
if(hasCaughtError()){
2004+
constunmountError=clearCaughtError();
2005+
captureCommitPhaseError(fiber,fiber.return,unmountError);
2006+
}
2007+
break;
2008+
}
2009+
}
2010+
}
2011+
}
2012+
18872013
export{
18882014
commitBeforeMutationLifeCycles,
18892015
commitResetTextContent,
@@ -1896,4 +2022,8 @@ export {
18962022
commitPassiveUnmount,
18972023
commitPassiveUnmountInsideDeletedTree,
18982024
commitPassiveMount,
2025+
invokeLayoutEffectMountInDEV,
2026+
invokeLayoutEffectUnmountInDEV,
2027+
invokePassiveEffectMountInDEV,
2028+
invokePassiveEffectUnmountInDEV,
18992029
};

packages/react-reconciler/src/ReactFiberFlags.js

+32-26
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,56 @@
1010
exporttypeFlags=number;
1111

1212
// Don't change these two values. They're used by React Dev Tools.
13-
exportconstNoFlags=/* */0b0000000000000000;
14-
exportconstPerformedWork=/* */0b0000000000000001;
13+
exportconstNoFlags=/* */0b000000000000000000;
14+
exportconstPerformedWork=/* */0b000000000000000001;
1515

1616
// You can change the rest (and add more).
17-
exportconstPlacement=/* */0b0000000000000010;
18-
exportconstUpdate=/* */0b0000000000000100;
19-
exportconstPlacementAndUpdate=/* */0b0000000000000110;
20-
exportconstDeletion=/* */0b0000000000001000;
21-
exportconstContentReset=/* */0b0000000000010000;
22-
exportconstCallback=/* */0b0000000000100000;
23-
exportconstDidCapture=/* */0b0000000001000000;
24-
exportconstRef=/* */0b0000000010000000;
25-
exportconstSnapshot=/* */0b0000000100000000;
26-
exportconstPassive=/* */0b0000001000000000;
17+
exportconstPlacement=/* */0b000000000000000010;
18+
exportconstUpdate=/* */0b000000000000000100;
19+
exportconstPlacementAndUpdate=/* */0b000000000000000110;
20+
exportconstDeletion=/* */0b000000000000001000;
21+
exportconstContentReset=/* */0b000000000000010000;
22+
exportconstCallback=/* */0b000000000000100000;
23+
exportconstDidCapture=/* */0b000000000001000000;
24+
exportconstRef=/* */0b000000000010000000;
25+
exportconstSnapshot=/* */0b000000000100000000;
26+
exportconstPassive=/* */0b000000001000000000;
2727
// TODO (effects) Remove this bit once the new reconciler is synced to the old.
28-
exportconstPassiveUnmountPendingDev=/* */0b0010000000000000;
29-
exportconstHydrating=/* */0b0000010000000000;
30-
exportconstHydratingAndUpdate=/* */0b0000010000000100;
28+
exportconstPassiveUnmountPendingDev=/* */0b000010000000000000;
29+
exportconstHydrating=/* */0b000000010000000000;
30+
exportconstHydratingAndUpdate=/* */0b000000010000000100;
3131

3232
// Passive & Update & Callback & Ref & Snapshot
33-
exportconstLifecycleEffectMask=/* */0b0000001110100100;
33+
exportconstLifecycleEffectMask=/* */0b000000001110100100;
3434

3535
// Union of all host effects
36-
exportconstHostEffectMask=/* */0b0000011111111111;
36+
exportconstHostEffectMask=/* */0b000000011111111111;
3737

3838
// These are not really side effects, but we still reuse this field.
39-
exportconstIncomplete=/* */0b0000100000000000;
40-
exportconstShouldCapture=/* */0b0001000000000000;
41-
exportconstForceUpdateForLegacySuspense=/* */0b0100000000000000;
39+
exportconstIncomplete=/* */0b000000100000000000;
40+
exportconstShouldCapture=/* */0b000001000000000000;
41+
exportconstForceUpdateForLegacySuspense=/* */0b000100000000000000;
4242

4343
// Static tags describe aspects of a fiber that are not specific to a render,
4444
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
4545
// This enables us to defer more work in the unmount case,
4646
// since we can defer traversing the tree during layout to look for Passive effects,
4747
// and instead rely on the static flag as a signal that there may be cleanup work.
48-
exportconstPassiveStatic=/* */0b1000000000000000;
48+
exportconstPassiveStatic=/* */0b001000000000000000;
4949

5050
// Union of side effect groupings as pertains to subtreeFlags
51-
exportconstBeforeMutationMask=/* */0b0000001100001010;
52-
exportconstMutationMask=/* */0b0000010010011110;
53-
exportconstLayoutMask=/* */0b0000000010100100;
54-
exportconstPassiveMask=/* */0b0000001000001000;
51+
exportconstBeforeMutationMask=/* */0b000000001100001010;
52+
exportconstMutationMask=/* */0b000000010010011110;
53+
exportconstLayoutMask=/* */0b000000000010100100;
54+
exportconstPassiveMask=/* */0b000000001000001000;
5555

5656
// Union of tags that don't get reset on clones.
5757
// This allows certain concepts to persist without recalculting them,
5858
// e.g. whether a subtree contains passive effects or portals.
59-
exportconstStaticMask=/* */0b1000000000000000;
59+
exportconstStaticMask=/* */0b001000000000000000;
60+
61+
// These flags allow us to traverse to fibers that have effects on mount
62+
// without traversing the entire tree after every commit for
63+
// double invoking
64+
exportconstMountLayoutDev=/* */0b010000000000000000;
65+
exportconstMountPassiveDev=/* */0b100000000000000000;

0 commit comments

Comments
 (0)
close