Skip to content

Commit 6a9a322

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

12 files changed

+97
-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
alwaysThrottleRetries,
4444
enableCreateEventHandleAPI,
45+
enablePersistedModeClonedFlag,
4546
enableProfilerTimer,
4647
enableProfilerCommitHooks,
4748
enableProfilerNestedUpdatePhase,
@@ -98,6 +99,7 @@ import {
9899
ShouldSuspendCommit,
99100
MaySuspendCommit,
100101
FormReset,
102+
Cloned,
101103
} from './ReactFiberFlags';
102104
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
103105
import {runWithFiberInDEV} from './ReactCurrentFiber';
@@ -2509,7 +2511,10 @@ function recursivelyTraverseMutationEffects(
25092511
}
25102512
}
25112513

2512-
if (parentFiber.subtreeFlags & MutationMask) {
2514+
if (
2515+
parentFiber.subtreeFlags &
2516+
(enablePersistedModeClonedFlag ? MutationMask | Cloned : MutationMask)
2517+
) {
25132518
let child = parentFiber.child;
25142519
while (child !== null) {
25152520
if (__DEV__) {

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,
@@ -90,6 +91,7 @@ import {
9091
MaySuspendCommit,
9192
ScheduleRetry,
9293
ShouldSuspendCommit,
94+
Cloned,
9395
} from './ReactFiberFlags';
9496

9597
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
}
@@ -450,6 +465,7 @@ function updateHostComponent(
450465

451466
let newChildSet = null;
452467
if (requiresClone && passChildrenWhenCloningPersistedNodes) {
468+
markCloned(workInProgress);
453469
newChildSet = createContainerChildSet();
454470
// If children might have changed, we have to add them all to the set.
455471
appendAllChildrenToContainer(
@@ -473,6 +489,8 @@ function updateHostComponent(
473489
// Note that this might release a previous clone.
474490
workInProgress.stateNode = currentInstance;
475491
return;
492+
} else {
493+
markCloned(workInProgress);
476494
}
477495

478496
// Certain renderers require commit-time effects for initial mount.
@@ -485,12 +503,14 @@ function updateHostComponent(
485503
}
486504
workInProgress.stateNode = newInstance;
487505
if (!requiresClone) {
488-
// If there are no other effects in this tree, we need to flag this node as having one.
489-
// Even though we're not going to use it for anything.
490-
// Otherwise parents won't know that there are new children to propagate upwards.
491-
markUpdate(workInProgress);
506+
if (!enablePersistedModeClonedFlag) {
507+
// If there are no other effects in this tree, we need to flag this node as having one.
508+
// Even though we're not going to use it for anything.
509+
// Otherwise parents won't know that there are new children to propagate upwards.
510+
markUpdate(workInProgress);
511+
}
492512
} else if (!passChildrenWhenCloningPersistedNodes) {
493-
// If children might have changed, we have to add them all to the set.
513+
// If children have changed, we have to add them all to the set.
494514
appendAllChildren(
495515
newInstance,
496516
workInProgress,
@@ -618,15 +638,18 @@ function updateHostText(
618638
// If the text content differs, we'll create a new text instance for it.
619639
const rootContainerInstance = getRootHostContainer();
620640
const currentHostContext = getHostContext();
641+
markCloned(workInProgress);
621642
workInProgress.stateNode = createTextInstance(
622643
newText,
623644
rootContainerInstance,
624645
currentHostContext,
625646
workInProgress,
626647
);
627-
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
628-
// This lets the parents know that at least one of their children has changed.
629-
markUpdate(workInProgress);
648+
if (!enablePersistedModeClonedFlag) {
649+
// We'll have to mark it as having an effect, even though we won't use the effect for anything.
650+
// This lets the parents know that at least one of their children has changed.
651+
markUpdate(workInProgress);
652+
}
630653
} else {
631654
workInProgress.stateNode = current.stateNode;
632655
}
@@ -1229,6 +1252,7 @@ function completeWork(
12291252
);
12301253
// TODO: For persistent renderers, we should pass children as part
12311254
// of the initial instance creation
1255+
markCloned(workInProgress);
12321256
appendAllChildren(instance, workInProgress, false, false);
12331257
workInProgress.stateNode = instance;
12341258

@@ -1284,6 +1308,7 @@ function completeWork(
12841308
if (wasHydrated) {
12851309
prepareToHydrateHostTextInstance(workInProgress);
12861310
} else {
1311+
markCloned(workInProgress);
12871312
workInProgress.stateNode = createTextInstance(
12881313
newText,
12891314
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
@@ -121,6 +121,12 @@ export const passChildrenWhenCloningPersistedNodes = false;
121121

122122
export const enableServerComponentLogs = __EXPERIMENTAL__;
123123

124+
/**
125+
* Enables a new Fiber flag used in persisted mode to reduce the number
126+
* of cloned host components.
127+
*/
128+
export const enablePersistedModeClonedFlag = false;
129+
124130
export const enableAddPropertiesFastPath = false;
125131

126132
export const enableOwnerStacks = __EXPERIMENTAL__;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ export const enableAddPropertiesFastPath = __VARIANT__;
2424
export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
2525
export const enableFastJSX = __VARIANT__;
2626
export const enableInfiniteRenderLoopDetection = __VARIANT__;
27+
export const enablePersistedModeClonedFlag = __VARIANT__;
2728
export const enableShallowPropDiffing = __VARIANT__;
2829
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const {
2626
enableDeferRootSchedulingToMicrotask,
2727
enableFastJSX,
2828
enableInfiniteRenderLoopDetection,
29+
enablePersistedModeClonedFlag,
2930
enableShallowPropDiffing,
3031
passChildrenWhenCloningPersistedNodes,
3132
} = dynamicFlags;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const enableLegacyFBSupport = false;
5656
export const enableLegacyHidden = false;
5757
export const enableNoCloningMemoCache = false;
5858
export const enableOwnerStacks = __EXPERIMENTAL__;
59+
export const enablePersistedModeClonedFlag = false;
5960
export const enablePostpone = false;
6061
export const enableReactTestRendererWarning = false;
6162
export const enableRefAsProp = true;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const enableAsyncActions = true;
7171
export const alwaysThrottleRetries = true;
7272

7373
export const passChildrenWhenCloningPersistedNodes = false;
74+
export const enablePersistedModeClonedFlag = false;
7475
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
7576
export const disableClientCache = true;
7677

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const enableLegacyFBSupport = false;
4848
export const enableLegacyHidden = false;
4949
export const enableNoCloningMemoCache = false;
5050
export const enableOwnerStacks = false;
51+
export const enablePersistedModeClonedFlag = false;
5152
export const enablePostpone = false;
5253
export const enableProfilerCommitHooks = __PROFILE__;
5354
export const enableProfilerNestedUpdatePhase = __PROFILE__;

0 commit comments

Comments
 (0)