Skip to content

Commit f828bad

Browse files
authored
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](react-native-community/discussions-and-proposals#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.
1 parent cd20376 commit f828bad

13 files changed

+392
-213
lines changed

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
findNodeHandle,
3939
dispatchCommand,
4040
sendAccessibilityEvent,
41+
getNodeFromInternalInstanceHandle,
4142
} from './ReactNativePublicCompat';
4243

4344
// $FlowFixMe[missing-local-annot]
@@ -119,6 +120,10 @@ export {
119120
// This export is typically undefined in production builds.
120121
// See the "enableGetInspectorDataForInstanceInProduction" flag.
121122
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,
122127
};
123128

124129
injectIntoDevTools({

packages/react-native-renderer/src/ReactFabricComponentTree.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ import {getPublicInstance} from './ReactFabricHostConfig';
2020
// This is ok in DOM because they types are interchangeable, but in React Native
2121
// they aren't.
2222
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+
2329
// $FlowFixMe[incompatible-return] DevTools incorrectly passes a fiber in React Native.
2430
return node;
2531
}
@@ -35,7 +41,7 @@ function getNodeFromInstance(fiber: Fiber): PublicInstance {
3541
}
3642

3743
function getFiberCurrentPropsFromNode(instance: Instance): Props {
38-
return instance.canonical.currentProps;
44+
return instance.currentProps;
3945
}
4046

4147
export {

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 38 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,13 @@
77
* @flow
88
*/
99

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';
2215
import {create, diff} from './ReactNativeAttributePayload';
23-
2416
import {dispatchEvent} from './ReactFabricEventEmitter';
25-
2617
import {
2718
DefaultEventPriority,
2819
DiscreteEventPriority,
@@ -31,7 +22,6 @@ import {
3122
// Modules provided by RN:
3223
import {
3324
ReactNativeViewConfigRegistry,
34-
TextInputState,
3525
deepFreezeAndThrowOnMutationInDev,
3626
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
3727

@@ -46,14 +36,9 @@ const {
4636
appendChildToSet: appendChildNodeToSet,
4737
completeRoot,
4838
registerEventHandler,
49-
measure: fabricMeasure,
50-
measureInWindow: fabricMeasureInWindow,
51-
measureLayout: fabricMeasureLayout,
5239
unstable_DefaultEventPriority: FabricDefaultPriority,
5340
unstable_DiscreteEventPriority: FabricDiscretePriority,
5441
unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
55-
setNativeProps,
56-
getBoundingClientRect: fabricGetBoundingClientRect,
5742
} = nativeFabricUIManager;
5843

5944
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
@@ -68,9 +53,15 @@ type Node = Object;
6853
export type Type = string;
6954
export type Props = Object;
7055
export type Instance = {
56+
// Reference to the shadow node.
7157
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,
7465
};
7566
export type TextInstance = {node: Node, ...};
7667
export type HydratableInstance = Instance | TextInstance;
@@ -104,137 +95,6 @@ if (registerEventHandler) {
10495
registerEventHandler(dispatchEvent);
10596
}
10697

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-
23898
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMutation';
23999
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
240100
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoScopes';
@@ -280,16 +140,19 @@ export function createInstance(
280140
internalInstanceHandle, // internalInstanceHandle
281141
);
282142

283-
const component = new ReactFabricHostComponent(
143+
const component = createPublicInstance(
284144
tag,
285145
viewConfig,
286-
props,
287146
internalInstanceHandle,
288147
);
289148

290149
return {
291150
node: node,
292-
canonical: component,
151+
nativeTag: tag,
152+
viewConfig,
153+
currentProps: props,
154+
internalInstanceHandle,
155+
publicInstance: component,
293156
};
294157
}
295158

@@ -359,12 +222,15 @@ export function getChildHostContext(
359222
}
360223

361224
export function getPublicInstance(instance: Instance): null | PublicInstance {
362-
if (instance.canonical) {
363-
return instance.canonical;
225+
if (instance.publicInstance != null) {
226+
return instance.publicInstance;
364227
}
365228

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]
367232
if (instance._nativeTag != null) {
233+
// $FlowExpectedError[incompatible-return]
368234
return instance;
369235
}
370236

@@ -383,12 +249,12 @@ export function prepareUpdate(
383249
newProps: Props,
384250
hostContext: HostContext,
385251
): null | Object {
386-
const viewConfig = instance.canonical.viewConfig;
252+
const viewConfig = instance.viewConfig;
387253
const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
388254
// TODO: If the event handlers have changed, we need to update the current props
389255
// in the commit phase but there is no host config hook to do it yet.
390256
// So instead we hack it by updating it in the render phase.
391-
instance.canonical.currentProps = newProps;
257+
instance.currentProps = newProps;
392258
return updatePayload;
393259
}
394260

@@ -467,7 +333,11 @@ export function cloneInstance(
467333
}
468334
return {
469335
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,
471341
};
472342
}
473343

@@ -477,15 +347,19 @@ export function cloneHiddenInstance(
477347
props: Props,
478348
internalInstanceHandle: Object,
479349
): Instance {
480-
const viewConfig = instance.canonical.viewConfig;
350+
const viewConfig = instance.viewConfig;
481351
const node = instance.node;
482352
const updatePayload = create(
483353
{style: {display: 'none'}},
484354
viewConfig.validAttributes,
485355
);
486356
return {
487357
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,
489363
};
490364
}
491365

0 commit comments

Comments
 (0)