@samitouri / QOS-React / commits / f83903bfcc

[RN] Set up test to create public instances lazily in Fabric (#32363)

## Summary In React Native, public instances and internal host nodes are not represented by the same object (ReactNativeElement & shadow nodes vs. just DOM elements), and the only one that's required for rendering is the shadow node. Public instances are generally only necessary when accessed via refs or events, and that usually happens for a small amount of components in the tree. This implements an optimization to create the public instance on demand, instead of eagerly creating it when creating the host node. We expect this to improve performance by reducing the logic we do per node and the number of object allocations. ## How did you test this change? Manually synced the changes to React Native and run Fantom tests and benchmarks, with the flag enabled and disabled. All tests pass in both cases, and benchmarks show a slight but consistent performance improvement.

Rubén Norte committed Feb 12, 2025 at 13:52 UTC f83903bfcc5a61811bd1b69b14f0ebbac4754462
10 files changed +61 -19
packages/react-native-renderer/src/ReactFiberConfigFabric.js
+50 -19
@@ -57,7 +57,10 @@ import {
57 getInspectorDataForInstance,
58 } from './ReactNativeFiberInspector';
59
60 -import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags';
60 +import {
61 + passChildrenWhenCloningPersistedNodes,
62 + enableLazyPublicInstanceInFabric,
63 +} from 'shared/ReactFeatureFlags';
64 import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
65 import type {ReactContext} from 'shared/ReactTypes';
66
@@ -93,8 +96,11 @@ export type Instance = {
96 currentProps: Props,
97 // Reference to the React handle (the fiber)
98 internalInstanceHandle: InternalInstanceHandle,
96 - // Exposed through refs.
97 - publicInstance: PublicInstance,
99 + // Exposed through refs. Potentially lazily created.
100 + publicInstance: PublicInstance | null,
101 + // This is only necessary to lazily create `publicInstance`.
102 + // Will be set to `null` after that is created.
103 + publicRootInstance?: PublicRootInstance | null,
104 },
105 };
106 export type TextInstance = {
@@ -186,23 +192,37 @@ export function createInstance(
192 internalInstanceHandle, // internalInstanceHandle
193 );
194
189 - const component = createPublicInstance(
190 - tag,
191 - viewConfig,
192 - internalInstanceHandle,
193 - rootContainerInstance.publicInstance,
194 - );
195 -
196 - return {
197 - node: node,
198 - canonical: {
199 - nativeTag: tag,
195 + if (enableLazyPublicInstanceInFabric) {
196 + return {
197 + node: node,
198 + canonical: {
199 + nativeTag: tag,
200 + viewConfig,
201 + currentProps: props,
202 + internalInstanceHandle,
203 + publicInstance: null,
204 + publicRootInstance: rootContainerInstance.publicInstance,
205 + },
206 + };
207 + } else {
208 + const component = createPublicInstance(
209 + tag,
210 viewConfig,
201 - currentProps: props,
211 internalInstanceHandle,
203 - publicInstance: component,
204 - },
205 - };
212 + rootContainerInstance.publicInstance,
213 + );
214 +
215 + return {
216 + node: node,
217 + canonical: {
218 + nativeTag: tag,
219 + viewConfig,
220 + currentProps: props,
221 + internalInstanceHandle,
222 + publicInstance: component,
223 + },
224 + };
225 + }
226 }
227
228 export function createTextInstance(
@@ -277,7 +297,18 @@ export function getChildHostContext(
297 }
298
299 export function getPublicInstance(instance: Instance): null | PublicInstance {
280 - if (instance.canonical != null && instance.canonical.publicInstance != null) {
300 + if (instance.canonical != null) {
301 + if (instance.canonical.publicInstance == null) {
302 + instance.canonical.publicInstance = createPublicInstance(
303 + instance.canonical.nativeTag,
304 + instance.canonical.viewConfig,
305 + instance.canonical.internalInstanceHandle,
306 + instance.canonical.publicRootInstance ?? null,
307 + );
308 + // This was only necessary to create the public instance.
309 + instance.canonical.publicRootInstance = null;
310 + }
311 +
312 return instance.canonical.publicInstance;
313 }
314
packages/shared/ReactFeatureFlags.js
+2
@@ -154,6 +154,8 @@ export const enableUseEffectCRUDOverload = false;
154
155 export const enableFastAddPropertiesInDiffing = true;
156
157 +export const enableLazyPublicInstanceInFabric = false;
158 +
159 // -----------------------------------------------------------------------------
160 // Ready for next major.
161 //
packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
+1
@@ -28,3 +28,4 @@ export const enableUseEffectCRUDOverload = __VARIANT__;
28 export const enableOwnerStacks = __VARIANT__;
29 export const enableRemoveConsolePatches = __VARIANT__;
30 export const enableFastAddPropertiesInDiffing = __VARIANT__;
31 +export const enableLazyPublicInstanceInFabric = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -30,6 +30,7 @@ export const {
30 enableOwnerStacks,
31 enableRemoveConsolePatches,
32 enableFastAddPropertiesInDiffing,
33 + enableLazyPublicInstanceInFabric,
34 } = dynamicFlags;
35
36 // The rest of the flags are static for better dead code elimination.
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -72,6 +72,7 @@ export const enableYieldingBeforePassive = false;
72 export const enableThrottledScheduling = false;
73 export const enableViewTransition = false;
74 export const enableFastAddPropertiesInDiffing = false;
75 +export const enableLazyPublicInstanceInFabric = false;
76
77 // Profiling Only
78 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -71,6 +71,7 @@ export const enableYieldingBeforePassive = true;
71 export const enableThrottledScheduling = false;
72 export const enableViewTransition = false;
73 export const enableFastAddPropertiesInDiffing = true;
74 +export const enableLazyPublicInstanceInFabric = false;
75
76 // TODO: This must be in sync with the main ReactFeatureFlags file because
77 // the Test Renderer's value must be the same as the one used by the
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -69,6 +69,7 @@ export const enableThrottledScheduling = false;
69 export const enableViewTransition = false;
70 export const enableRemoveConsolePatches = false;
71 export const enableFastAddPropertiesInDiffing = false;
72 +export const enableLazyPublicInstanceInFabric = false;
73
74 // Flow magic to verify the exports of this file match the original version.
75 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -84,6 +84,7 @@ export const enableThrottledScheduling = false;
84 export const enableViewTransition = false;
85 export const enableRemoveConsolePatches = false;
86 export const enableFastAddPropertiesInDiffing = false;
87 +export const enableLazyPublicInstanceInFabric = false;
88
89 // Flow magic to verify the exports of this file match the original version.
90 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -39,6 +39,7 @@ export const enableSiblingPrerendering = __VARIANT__;
39 export const enableUseEffectCRUDOverload = __VARIANT__;
40 export const enableRemoveConsolePatches = __VARIANT__;
41 export const enableFastAddPropertiesInDiffing = __VARIANT__;
42 +export const enableLazyPublicInstanceInFabric = false;
43 export const enableViewTransition = __VARIANT__;
44
45 // TODO: These flags are hard-coded to the default values used in open source.
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -110,5 +110,7 @@ export const disableLegacyMode = true;
110
111 export const enableShallowPropDiffing = false;
112
113 +export const enableLazyPublicInstanceInFabric = false;
114 +
115 // Flow magic to verify the exports of this file match the original version.
116 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);