@samitouri / QOS-React-2 / commits / f828bad387

Extracted definition and access to public instances to a separate module in Fabric (#26321)

## Summary The current definition of `Instance` in Fabric has 2 fields: - `node`: reference to the native node in the shadow tree. - `canonical`: public instance provided to users via refs + some internal fields needed by Fabric. We're currently using `canonical` not only as the public instance, but also to store internal properties that Fabric needs to access in different parts of the codebase. Those properties are, in fact, available through refs as well, which breaks encapsulation. This PR splits that into 2 separate fields, leaving the definition of instance as: - `node`: reference to the native node in the shadow tree. - `publicInstance`: public instance provided to users via refs. - Rest of internal fields needed by Fabric at the instance level. This also migrates all the current usages of `canonical` to use the right property depending on the use case. To improve encapsulation (and in preparation for the implementation of this [proposal to bring some DOM APIs to public instances in React Native](https://github.com/react-native-community/discussions-and-proposals/pull/607)), this also **moves the creation of and the access to the public instance to separate modules** (`ReactFabricPublicInstance` and `ReactFabricPublicInstanceUtils`). In a following diff, that module will be moved into the `react-native` repository and we'll access it through `ReactNativePrivateInterface`. ## How did you test this change? Existing unit tests. Manually synced the PR in Meta infra and tested in Catalyst + the integration with DevTools. Everything is working normally.

Rubén Norte committed Mar 13, 2023 at 13:25 UTC f828bad387f573c462d2a58afb38aea8bdbe9cb5
13 files changed +392 -213
packages/react-native-renderer/src/ReactFabric.js
+5
@@ -38,6 +38,7 @@ import {
38 findNodeHandle,
39 dispatchCommand,
40 sendAccessibilityEvent,
41 + getNodeFromInternalInstanceHandle,
42 } from './ReactNativePublicCompat';
43
44 // $FlowFixMe[missing-local-annot]
@@ -119,6 +120,10 @@ export {
120 // This export is typically undefined in production builds.
121 // See the "enableGetInspectorDataForInstanceInProduction" flag.
122 getInspectorDataForInstance,
123 + // The public instance has a reference to the internal instance handle.
124 + // This method allows it to acess the most recent shadow node for
125 + // the instance (it's only accessible through it).
126 + getNodeFromInternalInstanceHandle,
127 };
128
129 injectIntoDevTools({
packages/react-native-renderer/src/ReactFabricComponentTree.js
+7 -1
@@ -20,6 +20,12 @@ import {getPublicInstance} from './ReactFabricHostConfig';
20 // This is ok in DOM because they types are interchangeable, but in React Native
21 // they aren't.
22 function getInstanceFromNode(node: Instance | TextInstance): Fiber | null {
23 + const instance: Instance = (node: $FlowFixMe); // In React Native, node is never a text instance
24 +
25 + if (instance.internalInstanceHandle != null) {
26 + return instance.internalInstanceHandle;
27 + }
28 +
29 // $FlowFixMe[incompatible-return] DevTools incorrectly passes a fiber in React Native.
30 return node;
31 }
@@ -35,7 +41,7 @@ function getNodeFromInstance(fiber: Fiber): PublicInstance {
41 }
42
43 function getFiberCurrentPropsFromNode(instance: Instance): Props {
38 - return instance.canonical.currentProps;
44 + return instance.currentProps;
45 }
46
47 export {
packages/react-native-renderer/src/ReactFabricHostConfig.js
+38 -164
@@ -7,22 +7,13 @@
7 * @flow
8 */
9
10 -import type {ElementRef} from 'react';
11 -import type {
12 - HostComponent,
13 - MeasureInWindowOnSuccessCallback,
14 - MeasureLayoutOnSuccessCallback,
15 - MeasureOnSuccessCallback,
16 - INativeMethods,
17 - ViewConfig,
18 - TouchedViewDataAtPoint,
19 -} from './ReactNativeTypes';
20 -
21 -import {warnForStyleProps} from './NativeMethodsMixinUtils';
10 +import type {TouchedViewDataAtPoint, ViewConfig} from './ReactNativeTypes';
11 +import {
12 + createPublicInstance,
13 + type ReactFabricHostComponent,
14 +} from './ReactFabricPublicInstance';
15 import {create, diff} from './ReactNativeAttributePayload';
23 -
16 import {dispatchEvent} from './ReactFabricEventEmitter';
25 -
17 import {
18 DefaultEventPriority,
19 DiscreteEventPriority,
@@ -31,7 +22,6 @@ import {
22 // Modules provided by RN:
23 import {
24 ReactNativeViewConfigRegistry,
34 - TextInputState,
25 deepFreezeAndThrowOnMutationInDev,
26 } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
27
@@ -46,14 +36,9 @@ const {
36 appendChildToSet: appendChildNodeToSet,
37 completeRoot,
38 registerEventHandler,
49 - measure: fabricMeasure,
50 - measureInWindow: fabricMeasureInWindow,
51 - measureLayout: fabricMeasureLayout,
39 unstable_DefaultEventPriority: FabricDefaultPriority,
40 unstable_DiscreteEventPriority: FabricDiscretePriority,
41 unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
55 - setNativeProps,
56 - getBoundingClientRect: fabricGetBoundingClientRect,
42 } = nativeFabricUIManager;
43
44 const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
@@ -68,9 +53,15 @@ type Node = Object;
53 export type Type = string;
54 export type Props = Object;
55 export type Instance = {
56 + // Reference to the shadow node.
57 node: Node,
72 - canonical: ReactFabricHostComponent,
73 - ...
58 + nativeTag: number,
59 + viewConfig: ViewConfig,
60 + currentProps: Props,
61 + // Reference to the React handle (the fiber)
62 + internalInstanceHandle: Object,
63 + // Exposed through refs.
64 + publicInstance: ReactFabricHostComponent,
65 };
66 export type TextInstance = {node: Node, ...};
67 export type HydratableInstance = Instance | TextInstance;
@@ -104,137 +95,6 @@ if (registerEventHandler) {
95 registerEventHandler(dispatchEvent);
96 }
97
107 -const noop = () => {};
108 -
109 -/**
110 - * This is used for refs on host components.
111 - */
112 -class ReactFabricHostComponent implements INativeMethods {
113 - _nativeTag: number;
114 - viewConfig: ViewConfig;
115 - currentProps: Props;
116 - _internalInstanceHandle: Object;
117 -
118 - constructor(
119 - tag: number,
120 - viewConfig: ViewConfig,
121 - props: Props,
122 - internalInstanceHandle: Object,
123 - ) {
124 - this._nativeTag = tag;
125 - this.viewConfig = viewConfig;
126 - this.currentProps = props;
127 - this._internalInstanceHandle = internalInstanceHandle;
128 - }
129 -
130 - blur() {
131 - TextInputState.blurTextInput(this);
132 - }
133 -
134 - focus() {
135 - TextInputState.focusTextInput(this);
136 - }
137 -
138 - measure(callback: MeasureOnSuccessCallback) {
139 - const node = getShadowNodeFromInternalInstanceHandle(
140 - this._internalInstanceHandle,
141 - );
142 - if (node != null) {
143 - fabricMeasure(node, callback);
144 - }
145 - }
146 -
147 - measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
148 - const node = getShadowNodeFromInternalInstanceHandle(
149 - this._internalInstanceHandle,
150 - );
151 - if (node != null) {
152 - fabricMeasureInWindow(node, callback);
153 - }
154 - }
155 -
156 - measureLayout(
157 - relativeToNativeNode: number | ElementRef<HostComponent<mixed>>,
158 - onSuccess: MeasureLayoutOnSuccessCallback,
159 - onFail?: () => void /* currently unused */,
160 - ) {
161 - if (
162 - typeof relativeToNativeNode === 'number' ||
163 - !(relativeToNativeNode instanceof ReactFabricHostComponent)
164 - ) {
165 - if (__DEV__) {
166 - console.error(
167 - 'Warning: ref.measureLayout must be called with a ref to a native component.',
168 - );
169 - }
170 -
171 - return;
172 - }
173 -
174 - const toStateNode = getShadowNodeFromInternalInstanceHandle(
175 - this._internalInstanceHandle,
176 - );
177 - const fromStateNode = getShadowNodeFromInternalInstanceHandle(
178 - relativeToNativeNode._internalInstanceHandle,
179 - );
180 -
181 - if (toStateNode != null && fromStateNode != null) {
182 - fabricMeasureLayout(
183 - toStateNode,
184 - fromStateNode,
185 - onFail != null ? onFail : noop,
186 - onSuccess != null ? onSuccess : noop,
187 - );
188 - }
189 - }
190 -
191 - unstable_getBoundingClientRect(): DOMRect {
192 - const node = getShadowNodeFromInternalInstanceHandle(
193 - this._internalInstanceHandle,
194 - );
195 - if (node != null) {
196 - const rect = fabricGetBoundingClientRect(node);
197 -
198 - if (rect) {
199 - return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
200 - }
201 - }
202 -
203 - // Empty rect if any of the above failed
204 - return new DOMRect(0, 0, 0, 0);
205 - }
206 -
207 - setNativeProps(nativeProps: Object) {
208 - if (__DEV__) {
209 - warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
210 - }
211 - const updatePayload = create(nativeProps, this.viewConfig.validAttributes);
212 -
213 - const node = getShadowNodeFromInternalInstanceHandle(
214 - this._internalInstanceHandle,
215 - );
216 - if (node != null && updatePayload != null) {
217 - setNativeProps(node, updatePayload);
218 - }
219 - }
220 -}
221 -
222 -type ParamOf<Fn> = $Call<<T>((arg: T) => mixed) => T, Fn>;
223 -type ShadowNode = ParamOf<(typeof nativeFabricUIManager)['measure']>;
224 -
225 -export function getShadowNodeFromInternalInstanceHandle(
226 - internalInstanceHandle: mixed,
227 -): ?ShadowNode {
228 - return (
229 - // $FlowExpectedError[incompatible-return] internalInstanceHandle is opaque but we need to make an exception here.
230 - internalInstanceHandle &&
231 - // $FlowExpectedError[incompatible-return]
232 - internalInstanceHandle.stateNode &&
233 - // $FlowExpectedError[incompatible-use]
234 - internalInstanceHandle.stateNode.node
235 - );
236 -}
237 -
98 export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMutation';
99 export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
100 export * from 'react-reconciler/src/ReactFiberHostConfigWithNoScopes';
@@ -280,16 +140,19 @@ export function createInstance(
140 internalInstanceHandle, // internalInstanceHandle
141 );
142
283 - const component = new ReactFabricHostComponent(
143 + const component = createPublicInstance(
144 tag,
145 viewConfig,
286 - props,
146 internalInstanceHandle,
147 );
148
149 return {
150 node: node,
292 - canonical: component,
151 + nativeTag: tag,
152 + viewConfig,
153 + currentProps: props,
154 + internalInstanceHandle,
155 + publicInstance: component,
156 };
157 }
158
@@ -359,12 +222,15 @@ export function getChildHostContext(
222 }
223
224 export function getPublicInstance(instance: Instance): null | PublicInstance {
362 - if (instance.canonical) {
363 - return instance.canonical;
225 + if (instance.publicInstance != null) {
226 + return instance.publicInstance;
227 }
228
366 - // For compatibility with Paper
229 + // For compatibility with the legacy renderer, in case it's used with Fabric
230 + // in the same app.
231 + // $FlowExpectedError[prop-missing]
232 if (instance._nativeTag != null) {
233 + // $FlowExpectedError[incompatible-return]
234 return instance;
235 }
236
@@ -383,12 +249,12 @@ export function prepareUpdate(
249 newProps: Props,
250 hostContext: HostContext,
251 ): null | Object {
386 - const viewConfig = instance.canonical.viewConfig;
252 + const viewConfig = instance.viewConfig;
253 const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
254 // TODO: If the event handlers have changed, we need to update the current props
255 // in the commit phase but there is no host config hook to do it yet.
256 // So instead we hack it by updating it in the render phase.
391 - instance.canonical.currentProps = newProps;
257 + instance.currentProps = newProps;
258 return updatePayload;
259 }
260
@@ -467,7 +333,11 @@ export function cloneInstance(
333 }
334 return {
335 node: clone,
470 - canonical: instance.canonical,
336 + nativeTag: instance.nativeTag,
337 + viewConfig: instance.viewConfig,
338 + currentProps: instance.currentProps,
339 + internalInstanceHandle: instance.internalInstanceHandle,
340 + publicInstance: instance.publicInstance,
341 };
342 }
343
@@ -477,7 +347,7 @@ export function cloneHiddenInstance(
347 props: Props,
348 internalInstanceHandle: Object,
349 ): Instance {
480 - const viewConfig = instance.canonical.viewConfig;
350 + const viewConfig = instance.viewConfig;
351 const node = instance.node;
352 const updatePayload = create(
353 {style: {display: 'none'}},
@@ -485,7 +355,11 @@ export function cloneHiddenInstance(
355 );
356 return {
357 node: cloneNodeWithNewProps(node, updatePayload),
488 - canonical: instance.canonical,
358 + nativeTag: instance.nativeTag,
359 + viewConfig: instance.viewConfig,
360 + currentProps: instance.currentProps,
361 + internalInstanceHandle: instance.internalInstanceHandle,
362 + publicInstance: instance.publicInstance,
363 };
364 }
365
packages/react-native-renderer/src/ReactFabricPublicInstance.js new
+153
@@ -0,0 +1,153 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow strict-local
8 + */
9 +
10 +import type {ElementRef} from 'react';
11 +import type {
12 + ViewConfig,
13 + INativeMethods,
14 + HostComponent,
15 + MeasureInWindowOnSuccessCallback,
16 + MeasureLayoutOnSuccessCallback,
17 + MeasureOnSuccessCallback,
18 +} from './ReactNativeTypes';
19 +
20 +import {TextInputState} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
21 +import {create} from './ReactNativeAttributePayload';
22 +import {warnForStyleProps} from './NativeMethodsMixinUtils';
23 +import {getNodeFromInternalInstanceHandle} from './ReactNativePublicCompat';
24 +
25 +const {
26 + measure: fabricMeasure,
27 + measureInWindow: fabricMeasureInWindow,
28 + measureLayout: fabricMeasureLayout,
29 + setNativeProps,
30 + getBoundingClientRect: fabricGetBoundingClientRect,
31 +} = nativeFabricUIManager;
32 +
33 +const noop = () => {};
34 +
35 +/**
36 + * This is used for refs on host components.
37 + */
38 +export class ReactFabricHostComponent implements INativeMethods {
39 + // These need to be accessible from `ReactFabricPublicInstanceUtils`.
40 + __nativeTag: number;
41 + __internalInstanceHandle: mixed;
42 +
43 + _viewConfig: ViewConfig;
44 +
45 + constructor(
46 + tag: number,
47 + viewConfig: ViewConfig,
48 + internalInstanceHandle: mixed,
49 + ) {
50 + this.__nativeTag = tag;
51 + this._viewConfig = viewConfig;
52 + this.__internalInstanceHandle = internalInstanceHandle;
53 + }
54 +
55 + blur() {
56 + TextInputState.blurTextInput(this);
57 + }
58 +
59 + focus() {
60 + TextInputState.focusTextInput(this);
61 + }
62 +
63 + measure(callback: MeasureOnSuccessCallback) {
64 + const node = getNodeFromInternalInstanceHandle(
65 + this.__internalInstanceHandle,
66 + );
67 + if (node != null) {
68 + fabricMeasure(node, callback);
69 + }
70 + }
71 +
72 + measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
73 + const node = getNodeFromInternalInstanceHandle(
74 + this.__internalInstanceHandle,
75 + );
76 + if (node != null) {
77 + fabricMeasureInWindow(node, callback);
78 + }
79 + }
80 +
81 + measureLayout(
82 + relativeToNativeNode: number | ElementRef<HostComponent<mixed>>,
83 + onSuccess: MeasureLayoutOnSuccessCallback,
84 + onFail?: () => void /* currently unused */,
85 + ) {
86 + if (
87 + typeof relativeToNativeNode === 'number' ||
88 + !(relativeToNativeNode instanceof ReactFabricHostComponent)
89 + ) {
90 + if (__DEV__) {
91 + console.error(
92 + 'Warning: ref.measureLayout must be called with a ref to a native component.',
93 + );
94 + }
95 +
96 + return;
97 + }
98 +
99 + const toStateNode = getNodeFromInternalInstanceHandle(
100 + this.__internalInstanceHandle,
101 + );
102 + const fromStateNode = getNodeFromInternalInstanceHandle(
103 + relativeToNativeNode.__internalInstanceHandle,
104 + );
105 +
106 + if (toStateNode != null && fromStateNode != null) {
107 + fabricMeasureLayout(
108 + toStateNode,
109 + fromStateNode,
110 + onFail != null ? onFail : noop,
111 + onSuccess != null ? onSuccess : noop,
112 + );
113 + }
114 + }
115 +
116 + unstable_getBoundingClientRect(): DOMRect {
117 + const node = getNodeFromInternalInstanceHandle(
118 + this.__internalInstanceHandle,
119 + );
120 + if (node != null) {
121 + const rect = fabricGetBoundingClientRect(node);
122 +
123 + if (rect) {
124 + return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
125 + }
126 + }
127 +
128 + // Empty rect if any of the above failed
129 + return new DOMRect(0, 0, 0, 0);
130 + }
131 +
132 + setNativeProps(nativeProps: {...}): void {
133 + if (__DEV__) {
134 + warnForStyleProps(nativeProps, this._viewConfig.validAttributes);
135 + }
136 + const updatePayload = create(nativeProps, this._viewConfig.validAttributes);
137 +
138 + const node = getNodeFromInternalInstanceHandle(
139 + this.__internalInstanceHandle,
140 + );
141 + if (node != null && updatePayload != null) {
142 + setNativeProps(node, updatePayload);
143 + }
144 + }
145 +}
146 +
147 +export function createPublicInstance(
148 + tag: number,
149 + viewConfig: ViewConfig,
150 + internalInstanceHandle: mixed,
151 +): ReactFabricHostComponent {
152 + return new ReactFabricHostComponent(tag, viewConfig, internalInstanceHandle);
153 +}
packages/react-native-renderer/src/ReactFabricPublicInstanceUtils.js new
+29
@@ -0,0 +1,29 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow strict-local
8 + */
9 +
10 +import type {ReactFabricHostComponent} from './ReactFabricPublicInstance';
11 +
12 +/**
13 + * IMPORTANT: This module is used in Paper and Fabric. It needs to be defined
14 + * outside of `ReactFabricPublicInstance` because that module requires
15 + * `nativeFabricUIManager` to be defined in the global scope (which does not
16 + * happen in Paper).
17 + */
18 +
19 +export function getNativeTagFromPublicInstance(
20 + publicInstance: ReactFabricHostComponent,
21 +): number {
22 + return publicInstance.__nativeTag;
23 +}
24 +
25 +export function getInternalInstanceHandleFromPublicInstance(
26 + publicInstance: ReactFabricHostComponent,
27 +): mixed {
28 + return publicInstance.__internalInstanceHandle;
29 +}
packages/react-native-renderer/src/ReactNativeComponentTree.js
+3 -2
@@ -25,8 +25,9 @@ function getTagFromInstance(inst) {
25 let nativeInstance = inst.stateNode;
26 let tag = nativeInstance._nativeTag;
27 if (tag === undefined) {
28 - nativeInstance = nativeInstance.canonical;
29 - tag = nativeInstance._nativeTag;
28 + // For compatibility with Fabric
29 + tag = nativeInstance.nativeTag;
30 + nativeInstance = nativeInstance.publicInstance;
31 }
32
33 if (!tag) {
packages/react-native-renderer/src/ReactNativeFiberInspector.js
+23 -11
@@ -20,6 +20,8 @@ import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
20 import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
21 import {enableGetInspectorDataForInstanceInProduction} from 'shared/ReactFeatureFlags';
22 import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
23 +import {getInternalInstanceHandleFromPublicInstance} from './ReactFabricPublicInstanceUtils';
24 +import {getNodeFromInternalInstanceHandle} from './ReactNativePublicCompat';
25
26 const emptyObject = {};
27 if (__DEV__) {
@@ -46,15 +48,15 @@ if (__DEV__ || enableGetInspectorDataForInstanceInProduction) {
48 props: getHostProps(fiber),
49 source: fiber._debugSource,
50 measure: callback => {
49 - // If this is Fabric, we'll find a ShadowNode and use that to measure.
51 + // If this is Fabric, we'll find a shadow node and use that to measure.
52 const hostFiber = findCurrentHostFiber(fiber);
51 - const shadowNode =
53 + const node =
54 hostFiber != null &&
55 hostFiber.stateNode !== null &&
56 hostFiber.stateNode.node;
57
56 - if (shadowNode) {
57 - nativeFabricUIManager.measure(shadowNode, callback);
58 + if (node) {
59 + nativeFabricUIManager.measure(node, callback);
60 } else {
61 return UIManager.measure(
62 getHostNode(fiber, findNodeHandle),
@@ -198,30 +200,40 @@ if (__DEV__) {
200 ): void {
201 let closestInstance = null;
202
201 - if (inspectedView._internalInstanceHandle != null) {
203 + const fabricInstanceHandle =
204 + getInternalInstanceHandleFromPublicInstance(inspectedView);
205 + const fabricNode =
206 + fabricInstanceHandle != null
207 + ? getNodeFromInternalInstanceHandle(fabricInstanceHandle)
208 + : null;
209 + if (fabricNode) {
210 // For Fabric we can look up the instance handle directly and measure it.
211 nativeFabricUIManager.findNodeAtPoint(
204 - inspectedView._internalInstanceHandle.stateNode.node,
212 + fabricNode,
213 locationX,
214 locationY,
215 internalInstanceHandle => {
208 - if (internalInstanceHandle == null) {
216 + const node =
217 + internalInstanceHandle != null
218 + ? getNodeFromInternalInstanceHandle(internalInstanceHandle)
219 + : null;
220 + if (internalInstanceHandle == null || node == null) {
221 callback({
222 pointerY: locationY,
223 frame: {left: 0, top: 0, width: 0, height: 0},
224 ...getInspectorDataForInstance(closestInstance),
225 });
226 + return;
227 }
228
229 closestInstance =
217 - internalInstanceHandle.stateNode.canonical._internalInstanceHandle;
230 + internalInstanceHandle.stateNode.internalInstanceHandle;
231
232 // Note: this is deprecated and we want to remove it ASAP. Keeping it here for React DevTools compatibility for now.
220 - const nativeViewTag =
221 - internalInstanceHandle.stateNode.canonical._nativeTag;
233 + const nativeViewTag = internalInstanceHandle.stateNode.nativeTag;
234
235 nativeFabricUIManager.measure(
224 - internalInstanceHandle.stateNode.node,
236 + node,
237 (x, y, width, height, pageX, pageY) => {
238 const inspectorData =
239 getInspectorDataForInstance(closestInstance);
packages/react-native-renderer/src/ReactNativeHostConfig.js
+2 -2
@@ -218,8 +218,8 @@ export function getChildHostContext(
218
219 export function getPublicInstance(instance: Instance): * {
220 // $FlowExpectedError[prop-missing] For compatibility with Fabric
221 - if (instance.canonical) {
222 - return instance.canonical;
221 + if (instance.publicInstance != null) {
222 + return instance.publicInstance;
223 }
224
225 return instance;
packages/react-native-renderer/src/ReactNativePublicCompat.js
+80 -23
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {HostComponent} from './ReactNativeTypes';
10 +import type {Node, HostComponent} from './ReactNativeTypes';
11 import type {ElementRef, ElementType} from 'react';
12
13 // Modules provided by RN:
@@ -23,6 +23,11 @@ import {
23 import ReactSharedInternals from 'shared/ReactSharedInternals';
24 import getComponentNameFromType from 'shared/getComponentNameFromType';
25
26 +import {
27 + getInternalInstanceHandleFromPublicInstance,
28 + getNativeTagFromPublicInstance,
29 +} from './ReactFabricPublicInstanceUtils';
30 +
31 const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
32
33 export function findHostInstance_DEPRECATED<TElementType: ElementType>(
@@ -45,19 +50,24 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>(
50 owner.stateNode._warnedAboutRefsInRender = true;
51 }
52 }
53 +
54 if (componentOrHandle == null) {
55 return null;
56 }
51 - // $FlowFixMe Flow has hardcoded values for React DOM that don't work with RN
57 +
58 + // For compatibility with Fabric instances
59 + if (componentOrHandle.publicInstance) {
60 + // $FlowExpectedError[incompatible-return] Can't refine componentOrHandle as a Fabric instance
61 + return componentOrHandle.publicInstance;
62 + }
63 +
64 + // For compatibility with legacy renderer instances
65 if (componentOrHandle._nativeTag) {
53 - // $FlowFixMe Flow has hardcoded values for React DOM that don't work with RN
66 + // $FlowFixMe[incompatible-exact] Necessary when running Flow on Fabric
67 + // $FlowFixMe[incompatible-return]
68 return componentOrHandle;
69 }
56 - // $FlowFixMe Flow has hardcoded values for React DOM that don't work with RN
57 - if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
58 - // $FlowFixMe Flow has hardcoded values for React DOM that don't work with RN
59 - return componentOrHandle.canonical;
60 - }
70 +
71 let hostInstance;
72 if (__DEV__) {
73 hostInstance = findHostInstanceWithWarning(
@@ -68,6 +78,7 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>(
78 hostInstance = findHostInstance(componentOrHandle);
79 }
80
81 + // findHostInstance handles legacy vs. Fabric differences correctly
82 // $FlowFixMe[incompatible-exact] we need to fix the definition of `HostComponent` to use NativeMethods as an interface, not as a type.
83 return hostInstance;
84 }
@@ -90,19 +101,32 @@ export function findNodeHandle(componentOrHandle: any): ?number {
101 owner.stateNode._warnedAboutRefsInRender = true;
102 }
103 }
104 +
105 if (componentOrHandle == null) {
106 return null;
107 }
108 +
109 if (typeof componentOrHandle === 'number') {
110 // Already a node handle
111 return componentOrHandle;
112 }
113 +
114 + // For compatibility with legacy renderer instances
115 if (componentOrHandle._nativeTag) {
116 return componentOrHandle._nativeTag;
117 }
103 - if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
104 - return componentOrHandle.canonical._nativeTag;
118 +
119 + // For compatibility with Fabric instances
120 + if (componentOrHandle.nativeTag != null) {
121 + return componentOrHandle.nativeTag;
122 }
123 +
124 + // For compatibility with Fabric public instances
125 + const nativeTag = getNativeTagFromPublicInstance(componentOrHandle);
126 + if (nativeTag) {
127 + return nativeTag;
128 + }
129 +
130 let hostInstance;
131 if (__DEV__) {
132 hostInstance = findHostInstanceWithWarning(
@@ -117,7 +141,14 @@ export function findNodeHandle(componentOrHandle: any): ?number {
141 return hostInstance;
142 }
143
120 - return hostInstance._nativeTag;
144 + // $FlowFixMe[prop-missing] For compatibility with legacy renderer instances
145 + if (hostInstance._nativeTag != null) {
146 + // $FlowFixMe[incompatible-return]
147 + return hostInstance._nativeTag;
148 + }
149 +
150 + // $FlowFixMe[incompatible-call] Necessary when running Flow on the legacy renderer
151 + return getNativeTagFromPublicInstance(hostInstance);
152 }
153
154 export function dispatchCommand(
@@ -125,7 +156,11 @@ export function dispatchCommand(
156 command: string,
157 args: Array<any>,
158 ) {
128 - if (handle._nativeTag == null) {
159 + const nativeTag =
160 + handle._nativeTag != null
161 + ? handle._nativeTag
162 + : getNativeTagFromPublicInstance(handle);
163 + if (nativeTag == null) {
164 if (__DEV__) {
165 console.error(
166 "dispatchCommand was called with a ref that isn't a " +
@@ -135,18 +170,25 @@ export function dispatchCommand(
170 return;
171 }
172
138 - if (handle._internalInstanceHandle != null) {
139 - const {stateNode} = handle._internalInstanceHandle;
140 - if (stateNode != null) {
141 - nativeFabricUIManager.dispatchCommand(stateNode.node, command, args);
173 + const internalInstanceHandle =
174 + getInternalInstanceHandleFromPublicInstance(handle);
175 +
176 + if (internalInstanceHandle != null) {
177 + const node = getNodeFromInternalInstanceHandle(internalInstanceHandle);
178 + if (node != null) {
179 + nativeFabricUIManager.dispatchCommand(node, command, args);
180 }
181 } else {
144 - UIManager.dispatchViewManagerCommand(handle._nativeTag, command, args);
182 + UIManager.dispatchViewManagerCommand(nativeTag, command, args);
183 }
184 }
185
186 export function sendAccessibilityEvent(handle: any, eventType: string) {
149 - if (handle._nativeTag == null) {
187 + const nativeTag =
188 + handle._nativeTag != null
189 + ? handle._nativeTag
190 + : getNativeTagFromPublicInstance(handle);
191 + if (nativeTag == null) {
192 if (__DEV__) {
193 console.error(
194 "sendAccessibilityEvent was called with a ref that isn't a " +
@@ -156,12 +198,27 @@ export function sendAccessibilityEvent(handle: any, eventType: string) {
198 return;
199 }
200
159 - if (handle._internalInstanceHandle != null) {
160 - const {stateNode} = handle._internalInstanceHandle;
161 - if (stateNode != null) {
162 - nativeFabricUIManager.sendAccessibilityEvent(stateNode.node, eventType);
201 + const internalInstanceHandle =
202 + getInternalInstanceHandleFromPublicInstance(handle);
203 + if (internalInstanceHandle != null) {
204 + const node = getNodeFromInternalInstanceHandle(internalInstanceHandle);
205 + if (node != null) {
206 + nativeFabricUIManager.sendAccessibilityEvent(node, eventType);
207 }
208 } else {
165 - legacySendAccessibilityEvent(handle._nativeTag, eventType);
209 + legacySendAccessibilityEvent(nativeTag, eventType);
210 }
211 }
212 +
213 +export function getNodeFromInternalInstanceHandle(
214 + internalInstanceHandle: mixed,
215 +): ?Node {
216 + return (
217 + // $FlowExpectedError[incompatible-return] internalInstanceHandle is opaque but we need to make an exception here.
218 + internalInstanceHandle &&
219 + // $FlowExpectedError[incompatible-return]
220 + internalInstanceHandle.stateNode &&
221 + // $FlowExpectedError[incompatible-use]
222 + internalInstanceHandle.stateNode.node
223 + );
224 +}
packages/react-native-renderer/src/ReactNativeTypes.js
+3
@@ -212,6 +212,8 @@ export type ReactNativeType = {
212 ...
213 };
214
215 +export opaque type Node = mixed;
216 +
217 export type ReactFabricType = {
218 findHostInstance_DEPRECATED<TElementType: ElementType>(
219 componentOrHandle: ?(ElementRef<TElementType> | number),
@@ -235,6 +237,7 @@ export type ReactFabricType = {
237 concurrentRoot: ?boolean,
238 ): ?ElementRef<ElementType>,
239 unmountComponentAtNode(containerTag: number): void,
240 + getNodeFromInternalInstanceHandle(internalInstanceHandle: mixed): ?Node,
241 ...
242 };
243
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+38 -2
@@ -15,6 +15,8 @@ let ReactFabric;
15 let createReactNativeComponentClass;
16 let StrictMode;
17 let act;
18 +let getNativeTagFromPublicInstance;
19 +let getInternalInstanceHandleFromPublicInstance;
20
21 const DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT =
22 "Warning: dispatchCommand was called with a ref that isn't a " +
@@ -40,6 +42,10 @@ describe('ReactFabric', () => {
42 createReactNativeComponentClass =
43 require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
44 .ReactNativeViewConfigRegistry.register;
45 + getNativeTagFromPublicInstance =
46 + require('../ReactFabricPublicInstanceUtils').getNativeTagFromPublicInstance;
47 + getInternalInstanceHandleFromPublicInstance =
48 + require('../ReactFabricPublicInstanceUtils').getInternalInstanceHandleFromPublicInstance;
49
50 act = require('internal-test-utils').act;
51 });
@@ -931,7 +937,7 @@ describe('ReactFabric', () => {
937 '\n in RCTView (at **)' +
938 '\n in ContainsStrictModeChild (at **)',
939 ]);
934 - expect(match).toBe(child._nativeTag);
940 + expect(match).toBe(getNativeTagFromPublicInstance(child));
941 });
942
943 it('findNodeHandle should warn if passed a component that is inside StrictMode', async () => {
@@ -968,7 +974,7 @@ describe('ReactFabric', () => {
974 '\n in RCTView (at **)' +
975 '\n in IsInStrictMode (at **)',
976 ]);
971 - expect(match).toBe(child._nativeTag);
977 + expect(match).toBe(getNativeTagFromPublicInstance(child));
978 });
979
980 it('should no-op if calling sendAccessibilityEvent on unmounted refs', async () => {
@@ -1002,4 +1008,34 @@ describe('ReactFabric', () => {
1008
1009 expect(nativeFabricUIManager.sendAccessibilityEvent).not.toBeCalled();
1010 });
1011 +
1012 + it('getNodeFromInternalInstanceHandle should return the correct shadow node', async () => {
1013 + const View = createReactNativeComponentClass('RCTView', () => ({
1014 + validAttributes: {foo: true},
1015 + uiViewClassName: 'RCTView',
1016 + }));
1017 +
1018 + let viewRef;
1019 + await act(() => {
1020 + ReactFabric.render(
1021 + <View
1022 + foo="test"
1023 + ref={ref => {
1024 + viewRef = ref;
1025 + }}
1026 + />,
1027 + 1,
1028 + );
1029 + });
1030 +
1031 + const expectedShadowNode =
1032 + nativeFabricUIManager.createNode.mock.results[0].value;
1033 + expect(expectedShadowNode).toEqual(expect.any(Object));
1034 +
1035 + const internalInstanceHandle =
1036 + getInternalInstanceHandleFromPublicInstance(viewRef);
1037 + expect(
1038 + ReactFabric.getNodeFromInternalInstanceHandle(internalInstanceHandle),
1039 + ).toBe(expectedShadowNode);
1040 + });
1041 });
packages/react-native-renderer/src/__tests__/ReactFabricAndNative-test.internal.js
+4 -1
@@ -16,6 +16,7 @@ let ReactNative;
16 let UIManager;
17 let createReactNativeComponentClass;
18 let ReactNativePrivateInterface;
19 +let getNativeTagFromPublicInstance;
20
21 describe('created with ReactFabric called with ReactNative', () => {
22 beforeEach(() => {
@@ -35,6 +36,8 @@ describe('created with ReactFabric called with ReactNative', () => {
36 createReactNativeComponentClass =
37 require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
38 .ReactNativeViewConfigRegistry.register;
39 + getNativeTagFromPublicInstance =
40 + require('../ReactFabricPublicInstanceUtils').getNativeTagFromPublicInstance;
41 });
42
43 it('find Fabric instances with the RN renderer', () => {
@@ -54,7 +57,7 @@ describe('created with ReactFabric called with ReactNative', () => {
57 ReactFabric.render(<Component ref={ref} />, 11);
58
59 const instance = ReactNative.findHostInstance_DEPRECATED(ref.current);
57 - expect(instance._nativeTag).toBe(2);
60 + expect(getNativeTagFromPublicInstance(instance)).toBe(2);
61 });
62
63 it('find Fabric nodes with the RN renderer', () => {
scripts/flow/react-native-host-hooks.js
+7 -7
@@ -178,19 +178,19 @@ declare var nativeFabricUIManager: {
178 dispatchCommand: (node: Object, command: string, args: Array<any>) => void,
179 sendAccessibilityEvent: (node: Object, eventTypeName: string) => void,
180
181 - measure: (node: Node, callback: __MeasureOnSuccessCallback) => void,
181 + measure: (node: Object, callback: __MeasureOnSuccessCallback) => void,
182 measureInWindow: (
183 - node: Node,
183 + node: Object,
184 callback: __MeasureInWindowOnSuccessCallback,
185 ) => void,
186 measureLayout: (
187 - node: Node,
188 - relativeNode: Node,
187 + node: Object,
188 + relativeNode: Object,
189 onFail: () => void,
190 onSuccess: __MeasureLayoutOnSuccessCallback,
191 ) => void,
192 getBoundingClientRect: (
193 - node: Node,
193 + node: Object,
194 ) => [
195 /* x:*/ number,
196 /* y:*/ number,
@@ -198,13 +198,13 @@ declare var nativeFabricUIManager: {
198 /* height:*/ number,
199 ],
200 findNodeAtPoint: (
201 - node: Node,
201 + node: Object,
202 locationX: number,
203 locationY: number,
204 callback: (Object) => void,
205 ) => void,
206 setIsJSResponder: (
207 - node: Node,
207 + node: Object,
208 isJsResponder: boolean,
209 blockNativeResponder: boolean,
210 ) => void,