Create `ReactNativeComponent` Abstract Class (#9814)
This creates `ReactNativeComponent`, the modern replacement for `NativeMethodsMixin`. This class is going to allow us to start migrating React Native off `React.createClass(...)` into a typesafe, class-based brave new world. This also introduces `IReactNativeComponent`, an interface that enforces consistency between `ReactNativeComponent` and `ReactNativeFiberHostComponent`.
Timothy Yung committed
May 30, 2017 at 21:55 UTC
3f75ea8cdc537e189eb43158128a5a0295103211
4 files changed
+255
-4
src/renderers/native/ReactNativeComponent.js
new
+246
@@ -0,0 +1,246 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeComponent
10
+ * @flow
11
+ * @format
12
+ */
13
+
14
+'use strict';
15
+
16
+const React = require('react');
17
+const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
18
+const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
19
+const TextInputState = require('TextInputState');
20
+const UIManager = require('UIManager');
21
+
22
+const findNodeHandle = require('findNodeHandle');
23
+
24
+const {mountSafeCallback} = require('NativeMethodsMixinUtils');
25
+
26
+import type {
27
+ MeasureInWindowOnSuccessCallback,
28
+ MeasureLayoutOnSuccessCallback,
29
+ MeasureOnSuccessCallback,
30
+ NativeMethodsMixinType,
31
+} from 'ReactNativeTypes';
32
+import type {
33
+ ReactNativeBaseComponentViewConfig,
34
+} from 'ReactNativeViewConfigRegistry';
35
+
36
+const findNumericNodeHandle = ReactNativeFeatureFlags.useFiber
37
+ ? require('findNumericNodeHandleFiber')
38
+ : require('findNumericNodeHandleStack');
39
+
40
+/**
41
+ * Superclass that provides methods to access the underlying native component.
42
+ * This can be useful when you want to focus a view or measure its dimensions.
43
+ *
44
+ * Methods implemented by this class are available on most default components
45
+ * provided by React Native. However, they are *not* available on composite
46
+ * components that are not directly backed by a native view. For more
47
+ * information, see [Direct Manipulation](docs/direct-manipulation.html).
48
+ *
49
+ * @abstract
50
+ */
51
+class ReactNativeComponent<DefaultProps, Props, State>
52
+ extends React.Component<DefaultProps, Props, State> {
53
+ static defaultProps: $Abstract<DefaultProps>;
54
+ props: Props;
55
+ state: $Abstract<State>;
56
+
57
+ /**
58
+ * Removes focus. This is the opposite of `focus()`.
59
+ */
60
+ blur(): void {
61
+ TextInputState.blurTextInput(findNumericNodeHandle(this));
62
+ }
63
+
64
+ /**
65
+ * Requests focus. The exact behavior depends on the platform and view.
66
+ */
67
+ focus(): void {
68
+ TextInputState.focusTextInput(findNumericNodeHandle(this));
69
+ }
70
+
71
+ /**
72
+ * Measures the on-screen location and dimensions. If successful, the callback
73
+ * will be called asynchronously with the following arguments:
74
+ *
75
+ * - x
76
+ * - y
77
+ * - width
78
+ * - height
79
+ * - pageX
80
+ * - pageY
81
+ *
82
+ * These values are not available until after natives rendering completes. If
83
+ * you need the measurements as soon as possible, consider using the
84
+ * [`onLayout` prop](docs/view.html#onlayout) instead.
85
+ */
86
+ measure(callback: MeasureOnSuccessCallback): void {
87
+ UIManager.measure(
88
+ findNumericNodeHandle(this),
89
+ mountSafeCallback(this, callback),
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Measures the on-screen location and dimensions. Even if the React Native
95
+ * root view is embedded within another native view, this method will give you
96
+ * the absolute coordinates measured from the window. If successful, the
97
+ * callback will be called asynchronously with the following arguments:
98
+ *
99
+ * - x
100
+ * - y
101
+ * - width
102
+ * - height
103
+ *
104
+ * These values are not available until after natives rendering completes.
105
+ */
106
+ measureInWindow(callback: MeasureInWindowOnSuccessCallback): void {
107
+ UIManager.measureInWindow(
108
+ findNumericNodeHandle(this),
109
+ mountSafeCallback(this, callback),
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Similar to [`measure()`](#measure), but the resulting location will be
115
+ * relative to the supplied ancestor's location.
116
+ *
117
+ * Obtain a native node handle with `ReactNative.findNodeHandle(component)`.
118
+ */
119
+ measureLayout(
120
+ relativeToNativeNode: number,
121
+ onSuccess: MeasureLayoutOnSuccessCallback,
122
+ onFail: () => void /* currently unused */,
123
+ ): void {
124
+ UIManager.measureLayout(
125
+ findNumericNodeHandle(this),
126
+ relativeToNativeNode,
127
+ mountSafeCallback(this, onFail),
128
+ mountSafeCallback(this, onSuccess),
129
+ );
130
+ }
131
+
132
+ /**
133
+ * This function sends props straight to native. They will not participate in
134
+ * future diff process - this means that if you do not include them in the
135
+ * next render, they will remain active (see [Direct
136
+ * Manipulation](docs/direct-manipulation.html)).
137
+ */
138
+ setNativeProps(nativeProps: Object): void {
139
+ injectedSetNativeProps(this, nativeProps);
140
+ }
141
+}
142
+
143
+// eslint-disable-next-line no-unused-expressions
144
+(ReactNativeComponent.prototype: NativeMethodsMixinType);
145
+
146
+// TODO (bvaughn) Inline this once ReactNativeStack is dropped.
147
+function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) {
148
+ // Class components don't have viewConfig -> validateAttributes.
149
+ // Nor does it make sense to set native props on a non-native component.
150
+ // Instead, find the nearest host component and set props on it.
151
+ // Use findNodeHandle() rather than ReactNative.findNodeHandle() because
152
+ // We want the instance/wrapper (not the native tag).
153
+ let maybeInstance;
154
+
155
+ // Fiber errors if findNodeHandle is called for an umounted component.
156
+ // Tests using ReactTestRenderer will trigger this case indirectly.
157
+ // Mimicking stack behavior, we should silently ignore this case.
158
+ // TODO Fix ReactTestRenderer so we can remove this try/catch.
159
+ try {
160
+ maybeInstance = findNodeHandle(componentOrHandle);
161
+ } catch (error) {}
162
+
163
+ // If there is no host component beneath this we should fail silently.
164
+ // This is not an error; it could mean a class component rendered null.
165
+ if (maybeInstance == null) {
166
+ return;
167
+ }
168
+
169
+ const viewConfig: ReactNativeBaseComponentViewConfig =
170
+ maybeInstance.viewConfig;
171
+
172
+ var updatePayload = ReactNativeAttributePayload.create(
173
+ nativeProps,
174
+ viewConfig.validAttributes,
175
+ );
176
+
177
+ UIManager.updateView(
178
+ maybeInstance._nativeTag,
179
+ viewConfig.uiViewClassName,
180
+ updatePayload,
181
+ );
182
+}
183
+
184
+// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
185
+function setNativePropsStack(componentOrHandle: any, nativeProps: Object) {
186
+ // Class components don't have viewConfig -> validateAttributes.
187
+ // Nor does it make sense to set native props on a non-native component.
188
+ // Instead, find the nearest host component and set props on it.
189
+ // Use findNodeHandle() rather than ReactNative.findNodeHandle() because
190
+ // We want the instance/wrapper (not the native tag).
191
+ let maybeInstance = findNodeHandle(componentOrHandle);
192
+
193
+ // If there is no host component beneath this we should fail silently.
194
+ // This is not an error; it could mean a class component rendered null.
195
+ if (maybeInstance == null) {
196
+ return;
197
+ }
198
+
199
+ let viewConfig: ReactNativeBaseComponentViewConfig;
200
+ if (maybeInstance.viewConfig !== undefined) {
201
+ // ReactNativeBaseComponent
202
+ viewConfig = maybeInstance.viewConfig;
203
+ } else if (
204
+ maybeInstance._instance !== undefined &&
205
+ maybeInstance._instance.viewConfig !== undefined
206
+ ) {
207
+ // ReactCompositeComponentWrapper
208
+ // Some instances (eg Text) define their own viewConfig
209
+ viewConfig = maybeInstance._instance.viewConfig;
210
+ } else {
211
+ // ReactCompositeComponentWrapper
212
+ // Other instances (eg TextInput) defer to their children's viewConfig
213
+ while (maybeInstance._renderedComponent !== undefined) {
214
+ maybeInstance = maybeInstance._renderedComponent;
215
+ }
216
+ viewConfig = maybeInstance.viewConfig;
217
+ }
218
+
219
+ const tag: number = typeof maybeInstance.getHostNode === 'function'
220
+ ? maybeInstance.getHostNode()
221
+ : maybeInstance._rootNodeID;
222
+
223
+ var updatePayload = ReactNativeAttributePayload.create(
224
+ nativeProps,
225
+ viewConfig.validAttributes,
226
+ );
227
+
228
+ UIManager.updateView(tag, viewConfig.uiViewClassName, updatePayload);
229
+}
230
+
231
+// Switching based on fiber vs stack to avoid a lot of inline checks at runtime.
232
+// HACK Normally this injection would be done by the renderer, but in this case
233
+// that would result in a cycle between ReactNative and NativeMethodsMixin.
234
+// We avoid requiring additional code for this injection so it's probably ok?
235
+// TODO (bvaughn) Remove this once ReactNativeStack is gone.
236
+let injectedSetNativeProps: (
237
+ componentOrHandle: any,
238
+ nativeProps: Object,
239
+) => void;
240
+if (ReactNativeFeatureFlags.useFiber) {
241
+ injectedSetNativeProps = setNativePropsFiber;
242
+} else {
243
+ injectedSetNativeProps = setNativePropsStack;
244
+}
245
+
246
+module.exports = ReactNativeComponent;
src/renderers/native/ReactNativeFiber.js
+3
-1
@@ -45,7 +45,9 @@ ReactFiberErrorLogger.injection.injectDialog(
45
ReactNativeFiberErrorDialog.showDialog,
46
);
47
48
-var ReactNative: ReactNativeType = {
48
+const ReactNative: ReactNativeType = {
49
+ NativeComponent: require('ReactNativeComponent'),
50
+
51
findNodeHandle: findNumericNodeHandle,
52
53
render(element: Element<any>, containerTag: any, callback: ?Function) {
src/renderers/native/ReactNativeStack.js
+2
@@ -34,6 +34,8 @@ var render = function(
34
};
35
36
var ReactNative: ReactNativeType = {
37
+ NativeComponent: require('ReactNativeComponent'),
38
+
39
hasReactNativeInitialized: false,
40
41
findNodeHandle: findNumericNodeHandle,
src/renderers/native/ReactNativeTypes.js
+4
-3
@@ -71,10 +71,11 @@ type SecretInternalsType = {
71
};
72
73
/**
74
- * Flat ReactNative renderer bundles are too big for Flow to parse effeciently.
75
- * Provide minimal Flow typing for the high-level RN API and call it a day.
76
- */
74
+ * Flat ReactNative renderer bundles are too big for Flow to parse efficiently.
75
+ * Provide minimal Flow typing for the high-level RN API and call it a day.
76
+ */
77
export type ReactNativeType = {
78
+ NativeComponent: any,
79
findNodeHandle(componentOrHandle: any): ?number,
80
render(
81
element: React.Element<any>,