@samitouri / QOS-React / commits / 3f75ea8cdc

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>,