Skip to content

Commit 6c2c437

Browse files
committed
add a Cloned flag to indicate a required clone in persistent mode
1 parent a493901 commit 6c2c437

13 files changed

+98
-12
lines changed

packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,47 @@ describe('ReactFabric', () => {
273273
expect(nativeFabricUIManager.completeRoot).toBeCalled();
274274
});
275275

276+
// @gate enablePersistedModeClonedFlag
277+
it('should not clone nodes when layout effects are used', async () => {
278+
const View = createReactNativeComponentClass('RCTView', () => ({
279+
validAttributes: {foo: true},
280+
uiViewClassName: 'RCTView',
281+
}));
282+
283+
const ComponentWithEffect = () => {
284+
// Same thing happens with `ref` and `useImperativeHandle`
285+
React.useLayoutEffect(() => {});
286+
return null;
287+
};
288+
289+
await act(() =>
290+
ReactFabric.render(
291+
<View>
292+
<ComponentWithEffect />
293+
</View>,
294+
11,
295+
),
296+
);
297+
expect(nativeFabricUIManager.completeRoot).toBeCalled();
298+
jest.clearAllMocks();
299+
300+
await act(() =>
301+
ReactFabric.render(
302+
<View>
303+
<ComponentWithEffect />
304+
</View>,
305+
11,
306+
),
307+
);
308+
expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
309+
expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
310+
expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
311+
expect(
312+
nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
313+
).not.toBeCalled();
314+
expect(nativeFabricUIManager.completeRoot).not.toBeCalled();
315+
});
316+
276317
it('should call dispatchCommand for native refs', async () => {
277318
const View = createReactNativeComponentClass('RCTView', () => ({
278319
validAttributes: {foo: true},

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import type {
4242
import {
4343
alwaysThrottleDisappearingFallbacks,
4444
enableCreateEventHandleAPI,
45+
enablePersistedModeClonedFlag,
4546
enableProfilerTimer,
4647
enableProfilerCommitHooks,
4748
enableProfilerNestedUpdatePhase,
@@ -97,6 +98,7 @@ import {
9798
Visibility,
9899
ShouldSuspendCommit,
99100
MaySuspendCommit,
101+
Cloned,
100102
} from './ReactFiberFlags';
101103
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
102104
import {
@@ -2507,7 +2509,10 @@ function recursivelyTraverseMutationEffects(
25072509
}
25082510

25092511
const prevDebugFiber = getCurrentDebugFiberInDEV();
2510-
if (parentFiber.subtreeFlags & MutationMask) {
2512+
if (
2513+
parentFiber.subtreeFlags &
2514+
(enablePersistedModeClonedFlag ? MutationMask | Cloned : MutationMask)
2515+
) {
25112516
let child = parentFiber.child;
25122517
while (child !== null) {
25132518
setCurrentDebugFiberInDEV(child);

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
enableLegacyHidden,
3636
enableSuspenseCallback,
3737
enableScopeAPI,
38+
enablePersistedModeClonedFlag,
3839
enableProfilerTimer,
3940
enableCache,
4041
enableTransitionTracing,
@@ -89,6 +90,7 @@ import {
8990
MaySuspendCommit,
9091
ScheduleRetry,
9192
ShouldSuspendCommit,
93+
Cloned,
9294
} from './ReactFiberFlags';
9395

9496
import {
@@ -182,6 +184,16 @@ function markUpdate(workInProgress: Fiber) {
182184
workInProgress.flags |= Update;
183185
}
184186

187+
/**
188+
* Tag the fiber with Cloned in persistent mode to signal that
189+
* it received an update that requires a clone of the tree above.
190+
*/
191+
function markCloned(workInProgress: Fiber) {
192+
if (supportsPersistence && enablePersistedModeClonedFlag) {
193+
workInProgress.flags |= Cloned;
194+
}
195+
}
196+
185197
/**
186198
* In persistent mode, return whether this update needs to clone the subtree.
187199
*/
@@ -199,9 +211,12 @@ function doesRequireClone(current: null | Fiber, completedWork: Fiber) {
199211
// then we only have to check the `completedWork.subtreeFlags`.
200212
let child = completedWork.child;
201213
while (child !== null) {
214+
const checkedFlags = enablePersistedModeClonedFlag
215+
? Cloned | Visibility | Placement
216+
: MutationMask;
202217
if (
203-
(child.flags & MutationMask) !== NoFlags ||
204-
(child.subtreeFlags & MutationMask) !== NoFlags
218+
(child.flags & checkedFlags) !== NoFlags ||
219+
(child.subtreeFlags & checkedFlags) !== NoFlags
205220
) {
206221
return true;
207222
}
@@ -451,6 +466,7 @@ function updateHostComponent(
451466

452467
let newChildSet = null;
453468
if (requiresClone && passChildrenWhenCloningPersistedNodes) {
469+
markCloned(workInProgress);
454470
newChildSet = createContainerChildSet();
455471
// If children might have changed, we have to add them all to the set.
456472
appendAllChildrenToContainer(
@@ -474,6 +490,8 @@ function updateHostComponent(
474490
// Note that this might release a previous clone.
475491
workInProgress.stateNode = currentInstance;
476492
return;
493+
} else {
494+
markCloned(workInProgress);
477495
}
478496

479497
// Certain renderers require commit-time effects for initial mount.
@@ -486,12 +504,14 @@ function updateHostComponent(
486504
}
487505
workInProgress.stateNode = newInstance;
488506
if (!requiresClone) {
489-
// If there are no other effects in this tree, we need to flag this node as having one.
490-
// Even though we're not going to use it for anything.
491-
// Otherwise parents won't know that there are new children to propagate upwards.
492-
markUpdate(workInProgress);
507+
if (!enablePersistedModeClonedFlag) {
508+
// If there are no other effects in this tree, we need to flag this node as having one.
509+
// Even though we're not going to use it for anything.
510+
// Otherwise parents won't know that there are new children to propagate upwards.
511+
markUpdate(workInProgress);
512+
}
493513
} else if (!passChildrenWhenCloningPersistedNodes) {
494-
// If children might have changed, we have to add them all to the set.
514+
// If children have changed, we have to add them all to the set.
495515
appendAllChildren(
496516
newInstance,
497517
workInProgress,
@@ -650,15 +670,18 @@ function updateHostText(
650670
// If the text content differs, we'll create a new text instance for it.
651671
const rootContainerInstance = getRootHostContainer();
652672
const currentHostContext = getHostContext();
673+
markCloned(workInProgress);
653674
workInProgress.stateNode = createTextInstance(
654675
newText,
655676
rootContainerInstance,
656677
currentHostContext,
657678
workInProgress,
658679
);
659-
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
660-
// This lets the parents know that at least one of their children has changed.
661-
markUpdate(workInProgress);
680+
if (!enablePersistedModeClonedFlag) {
681+
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
682+
// This lets the parents know that at least one of their children has changed.
683+
markUpdate(workInProgress);
684+
}
662685
} else {
663686
workInProgress.stateNode = current.stateNode;
664687
}
@@ -1254,6 +1277,7 @@ function completeWork(
12541277
);
12551278
// TODO: For persistent renderers, we should pass children as part
12561279
// of the initial instance creation
1280+
markCloned(workInProgress);
12571281
appendAllChildren(instance, workInProgress, false, false);
12581282
workInProgress.stateNode = instance;
12591283

@@ -1311,6 +1335,7 @@ function completeWork(
13111335
markUpdate(workInProgress);
13121336
}
13131337
} else {
1338+
markCloned(workInProgress);
13141339
workInProgress.stateNode = createTextInstance(
13151340
newText,
13161341
rootContainerInstance,

packages/react-reconciler/src/ReactFiberFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const Hydrating = /* */ 0b0000000000000001000000000000
2020

2121
// You can change the rest (and add more).
2222
export const Update = /* */ 0b0000000000000000000000000100;
23-
/* Skipped value: 0b0000000000000000000000001000; */
23+
export const Cloned = /* */ 0b0000000000000000000000001000;
2424

2525
export const ChildDeletion = /* */ 0b0000000000000000000000010000;
2626
export const ContentReset = /* */ 0b0000000000000000000000100000;

packages/shared/ReactFeatureFlags.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ export const alwaysThrottleRetries = true;
119119

120120
export const passChildrenWhenCloningPersistedNodes = false;
121121

122+
/**
123+
* Enables a new Fiber flag used in persisted mode to reduce the number
124+
* of cloned host components.
125+
*/
126+
export const enablePersistedModeClonedFlag = false;
127+
122128
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
123129

124130
export const enableRenderableContext = false;

packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const enableRenderableContext = __VARIANT__;
3131
export const enableUnifiedSyncLane = __VARIANT__;
3232
export const enableUseRefAccessWarning = __VARIANT__;
3333
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
34+
export const enablePersistedModeClonedFlag = __VARIANT__;
3435
export const useModernStrictMode = __VARIANT__;
3536

3637
// Flow magic to verify the exports of this file match the original version.

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const {
2929
enableUnifiedSyncLane,
3030
enableUseRefAccessWarning,
3131
passChildrenWhenCloningPersistedNodes,
32+
enablePersistedModeClonedFlag,
3233
useModernStrictMode,
3334
} = dynamicFlags;
3435

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const alwaysThrottleDisappearingFallbacks = false;
9090
export const alwaysThrottleRetries = false;
9191

9292
export const passChildrenWhenCloningPersistedNodes = false;
93+
export const enablePersistedModeClonedFlag = false;
9394
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
9495
export const disableClientCache = true;
9596

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const alwaysThrottleDisappearingFallbacks = true;
7979
export const alwaysThrottleRetries = true;
8080

8181
export const passChildrenWhenCloningPersistedNodes = false;
82+
export const enablePersistedModeClonedFlag = false;
8283
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
8384
export const disableClientCache = true;
8485

packages/shared/forks/ReactFeatureFlags.test-renderer.native.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const alwaysThrottleDisappearingFallbacks = true;
8181
export const alwaysThrottleRetries = true;
8282

8383
export const passChildrenWhenCloningPersistedNodes = false;
84+
export const enablePersistedModeClonedFlag = false;
8485
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
8586
export const disableClientCache = true;
8687

0 commit comments

Comments
 (0)