Skip to content

Commit 9344f3a

Browse files
Brian Vaughnfacebook-github-bot
Brian Vaughn
authored andcommitted
Support string return type from RN createReactNativeFiberComponentClass()
Reviewed By: sebmarkbage Differential Revision: D4607283 fbshipit-source-id: 466d2373dd570f77ebcced306d2f20a3f72d79c6
1 parent d731466 commit 9344f3a

13 files changed

+424
-130
lines changed

Libraries/Components/TextInput/TextInput.js

-7
Original file line numberDiff line numberDiff line change
@@ -540,13 +540,6 @@ const TextInput = React.createClass({
540540
*/
541541
mixins: [NativeMethodsMixin, TimerMixin],
542542

543-
viewConfig:
544-
((Platform.OS === 'ios' && RCTTextField ?
545-
RCTTextField.viewConfig :
546-
(Platform.OS === 'android' && AndroidTextInput ?
547-
AndroidTextInput.viewConfig :
548-
{})) : Object),
549-
550543
/**
551544
* Returns `true` if the input is currently focused; `false` otherwise.
552545
*/

Libraries/Components/View/View.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const NativeMethodsMixin = require('NativeMethodsMixin');
1616
const NativeModules = require('NativeModules');
1717
const Platform = require('Platform');
1818
const React = require('React');
19+
const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
1920
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
2021
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
2122
const StyleSheetPropType = require('StyleSheetPropType');
@@ -119,6 +120,9 @@ const View = React.createClass({
119120
...statics,
120121
},
121122

123+
// TODO (bvaughn) Replace this with a deprecated getter warning. This object
124+
// should be accessible via a separate import. It will not be available in
125+
// production mode in the future and so should not be directly accessed.
122126
propTypes: {
123127
...TVViewPropTypes,
124128

@@ -536,11 +540,20 @@ if (__DEV__) {
536540
}
537541
}
538542

543+
// TODO (bvaughn) Remove feature flags once all static View accessors are gone.
544+
// We temporarily wrap fiber native views with the create-class View above,
545+
// Because external code sometimes accesses static properties of this view.
539546
let ViewToExport = RCTView;
540-
if (__DEV__) {
547+
if (
548+
__DEV__ ||
549+
ReactNativeFeatureFlags.useFiber
550+
) {
541551
ViewToExport = View;
542552
} else {
543-
Object.assign(RCTView, statics);
553+
// TODO (bvaughn) Remove this mixin once all static View accessors are gone.
554+
Object.assign((RCTView : any), statics);
544555
}
545556

546-
module.exports = ViewToExport;
557+
// TODO (bvaughn) Temporarily mask Flow warnings for View property accesses.
558+
// We're wrapping the string type (Fiber) for now to avoid any actual problems.
559+
module.exports = ((ViewToExport : any) : typeof View);

Libraries/ReactNative/UIManager.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ invariant(UIManager, 'UIManager is undefined. The native module config is probab
2626

2727
const _takeSnapshot = UIManager.takeSnapshot;
2828

29+
// findNodeHandle() returns a reference to a wrapper component with viewConfig.
30+
// This wrapper is required for NativeMethodsMixin.setNativeProps, but most
31+
// callers want the native tag (number) and not the wrapper. For this purpose,
32+
// the ReactNative renderer decorates findNodeHandle() and extracts the tag.
33+
// However UIManager can't require ReactNative without introducing a cycle, and
34+
// deferring the require causes a significant performance regression in Wilde
35+
// (along the lines of 17% regression in RN Bridge startup). So as a temporary
36+
// workaround, this wrapper method mimics what the native renderer does.
37+
// TODO (bvaughn) Remove this and use findNodeHandle directly once stack is gone
38+
function findNodeHandleWrapper(componentOrHandle : any) : ?number {
39+
const instance: any = findNodeHandle(componentOrHandle);
40+
41+
if (instance) {
42+
return typeof instance._nativeTag === 'number'
43+
? instance._nativeTag
44+
: instance.getHostNode();
45+
} else {
46+
return null;
47+
}
48+
}
49+
2950
/**
3051
* Capture an image of the screen, window or an individual view. The image
3152
* will be stored in a temporary file that will only exist for as long as the
@@ -57,7 +78,7 @@ UIManager.takeSnapshot = async function(
5778
return;
5879
}
5980
if (typeof view !== 'number' && view !== 'window') {
60-
view = findNodeHandle(view) || 'window';
81+
view = findNodeHandleWrapper(view) || 'window';
6182
}
6283
return _takeSnapshot(view, options);
6384
};

Libraries/ReactNative/requireNativeComponent.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function requireNativeComponent(
4646
viewName: string,
4747
componentInterface?: ?ComponentInterface,
4848
extraConfig?: ?{nativeOnly?: Object},
49-
): Function {
49+
): ReactClass<any> | string {
5050
const viewConfig = UIManager[viewName];
5151
if (!viewConfig || !viewConfig.NativeProps) {
5252
warning(false, 'Native component for "%s" does not exist', viewName);

Libraries/Renderer/src/renderers/native/NativeMethodsMixin.js

+133-73
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,25 @@
1313

1414
var ReactNative = require('ReactNative');
1515
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
16+
var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags');
1617
var TextInputState = require('TextInputState');
1718
var UIManager = require('UIManager');
1819

1920
var invariant = require('fbjs/lib/invariant');
21+
var findNodeHandle = require('findNodeHandle');
2022

21-
type MeasureOnSuccessCallback = (
22-
x: number,
23-
y: number,
24-
width: number,
25-
height: number,
26-
pageX: number,
27-
pageY: number
28-
) => void
29-
30-
type MeasureInWindowOnSuccessCallback = (
31-
x: number,
32-
y: number,
33-
width: number,
34-
height: number,
35-
) => void
36-
37-
type MeasureLayoutOnSuccessCallback = (
38-
left: number,
39-
top: number,
40-
width: number,
41-
height: number
42-
) => void
43-
44-
function warnForStyleProps(props, validAttributes) {
45-
for (var key in validAttributes.style) {
46-
if (!(validAttributes[key] || props[key] === undefined)) {
47-
console.error(
48-
'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
49-
'should nest it in a style object. ' +
50-
'E.g. `{ style: { ' + key + ': ... } }`'
51-
);
52-
}
53-
}
54-
}
23+
var {
24+
mountSafeCallback,
25+
throwOnStylesProp,
26+
warnForStyleProps,
27+
} = require('NativeMethodsMixinUtils');
28+
29+
import type {
30+
MeasureInWindowOnSuccessCallback,
31+
MeasureLayoutOnSuccessCallback,
32+
MeasureOnSuccessCallback,
33+
} from 'NativeMethodsMixinUtils';
34+
import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry';
5535

5636
/**
5737
* `NativeMethodsMixin` provides methods to access the underlying native
@@ -65,6 +45,10 @@ function warnForStyleProps(props, validAttributes) {
6545
* information, see [Direct
6646
* Manipulation](docs/direct-manipulation.html).
6747
*/
48+
// TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to-
49+
// ensure that these mixins and ReactNativeFiberHostComponent stay in sync.
50+
// Unfortunately, using it causes Flow to complain WRT createClass mixins:
51+
// "call of method `createClass`. Expected an exact object instead of ..."
6852
var NativeMethodsMixin = {
6953
/**
7054
* Determines the location on screen, width, and height of the given view and
@@ -140,20 +124,15 @@ var NativeMethodsMixin = {
140124
* Manipulation](docs/direct-manipulation.html)).
141125
*/
142126
setNativeProps: function(nativeProps: Object) {
143-
if (__DEV__) {
144-
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
145-
}
127+
// Ensure ReactNative factory function has configured findNodeHandle.
128+
// Requiring it won't execute the factory function until first referenced.
129+
// It's possible for tests that use ReactTestRenderer to reach this point,
130+
// Without having executed ReactNative.
131+
// Defer the factory function until now to avoid a cycle with UIManager.
132+
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
133+
require('ReactNative');
146134

147-
var updatePayload = ReactNativeAttributePayload.create(
148-
nativeProps,
149-
this.viewConfig.validAttributes
150-
);
151-
152-
UIManager.updateView(
153-
(ReactNative.findNodeHandle(this) : any),
154-
this.viewConfig.uiViewClassName,
155-
updatePayload
156-
);
135+
injectedSetNativeProps(this, nativeProps);
157136
},
158137

159138
/**
@@ -172,19 +151,116 @@ var NativeMethodsMixin = {
172151
},
173152
};
174153

175-
function throwOnStylesProp(component, props) {
176-
if (props.styles !== undefined) {
177-
var owner = component._owner || null;
178-
var name = component.constructor.displayName;
179-
var msg = '`styles` is not a supported property of `' + name + '`, did ' +
180-
'you mean `style` (singular)?';
181-
if (owner && owner.constructor && owner.constructor.displayName) {
182-
msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' +
183-
' component.';
154+
// TODO (bvaughn) Inline this once ReactNativeStack is dropped.
155+
function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) {
156+
// Class components don't have viewConfig -> validateAttributes.
157+
// Nor does it make sense to set native props on a non-native component.
158+
// Instead, find the nearest host component and set props on it.
159+
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
160+
// We want the instance/wrapper (not the native tag).
161+
let maybeInstance;
162+
163+
// Fiber errors if findNodeHandle is called for an umounted component.
164+
// Tests using ReactTestRenderer will trigger this case indirectly.
165+
// Mimicking stack behavior, we should silently ignore this case.
166+
// TODO Fix ReactTestRenderer so we can remove this try/catch.
167+
try {
168+
maybeInstance = findNodeHandle(componentOrHandle);
169+
} catch (error) {}
170+
171+
// If there is no host component beneath this we should fail silently.
172+
// This is not an error; it could mean a class component rendered null.
173+
if (maybeInstance == null) {
174+
return;
175+
}
176+
177+
const viewConfig : ReactNativeBaseComponentViewConfig =
178+
maybeInstance.viewConfig;
179+
180+
if (__DEV__) {
181+
warnForStyleProps(nativeProps, viewConfig.validAttributes);
182+
}
183+
184+
var updatePayload = ReactNativeAttributePayload.create(
185+
nativeProps,
186+
viewConfig.validAttributes,
187+
);
188+
189+
UIManager.updateView(
190+
maybeInstance._nativeTag,
191+
viewConfig.uiViewClassName,
192+
updatePayload,
193+
);
194+
}
195+
196+
// TODO (bvaughn) Remove this once ReactNativeStack is dropped.
197+
function setNativePropsStack(componentOrHandle: any, nativeProps: Object) {
198+
// Class components don't have viewConfig -> validateAttributes.
199+
// Nor does it make sense to set native props on a non-native component.
200+
// Instead, find the nearest host component and set props on it.
201+
// Use findNodeHandle() rather than ReactNative.findNodeHandle() because
202+
// We want the instance/wrapper (not the native tag).
203+
let maybeInstance = findNodeHandle(componentOrHandle);
204+
205+
// If there is no host component beneath this we should fail silently.
206+
// This is not an error; it could mean a class component rendered null.
207+
if (maybeInstance == null) {
208+
return;
209+
}
210+
211+
let viewConfig : ReactNativeBaseComponentViewConfig;
212+
if (maybeInstance.viewConfig !== undefined) {
213+
// ReactNativeBaseComponent
214+
viewConfig = maybeInstance.viewConfig;
215+
} else if (
216+
maybeInstance._instance !== undefined &&
217+
maybeInstance._instance.viewConfig !== undefined
218+
) {
219+
// ReactCompositeComponentWrapper
220+
// Some instances (eg Text) define their own viewConfig
221+
viewConfig = maybeInstance._instance.viewConfig;
222+
} else {
223+
// ReactCompositeComponentWrapper
224+
// Other instances (eg TextInput) defer to their children's viewConfig
225+
while (maybeInstance._renderedComponent !== undefined) {
226+
maybeInstance = maybeInstance._renderedComponent;
184227
}
185-
throw new Error(msg);
228+
viewConfig = maybeInstance.viewConfig;
229+
}
230+
231+
const tag : number = typeof maybeInstance.getHostNode === 'function'
232+
? maybeInstance.getHostNode()
233+
: maybeInstance._rootNodeID;
234+
235+
if (__DEV__) {
236+
warnForStyleProps(nativeProps, viewConfig.validAttributes);
186237
}
238+
239+
var updatePayload = ReactNativeAttributePayload.create(
240+
nativeProps,
241+
viewConfig.validAttributes,
242+
);
243+
244+
UIManager.updateView(
245+
tag,
246+
viewConfig.uiViewClassName,
247+
updatePayload,
248+
);
249+
}
250+
251+
// Switching based on fiber vs stack to avoid a lot of inline checks at runtime.
252+
// HACK Normally this injection would be done by the renderer, but in this case
253+
// that would result in a cycle between ReactNative and NativeMethodsMixin.
254+
// We avoid requiring additional code for this injection so it's probably ok?
255+
// TODO (bvaughn) Remove this once ReactNativeStack is gone.
256+
let injectedSetNativeProps :
257+
(componentOrHandle: any, nativeProps: Object) => void;
258+
if (ReactNativeFeatureFlags.useFiber) {
259+
injectedSetNativeProps = setNativePropsFiber;
260+
} else {
261+
injectedSetNativeProps = setNativePropsStack;
187262
}
263+
188264
if (__DEV__) {
189265
// hide this from Flow since we can't define these properties outside of
190266
// __DEV__ without actually implementing them (setting them to undefined
@@ -203,20 +279,4 @@ if (__DEV__) {
203279
};
204280
}
205281

206-
/**
207-
* In the future, we should cleanup callbacks by cancelling them instead of
208-
* using this.
209-
*/
210-
function mountSafeCallback(
211-
context: ReactComponent<any, any, any>,
212-
callback: ?Function
213-
): any {
214-
return function() {
215-
if (!callback || (typeof context.isMounted === 'function' && !context.isMounted())) {
216-
return undefined;
217-
}
218-
return callback.apply(context, arguments);
219-
};
220-
}
221-
222282
module.exports = NativeMethodsMixin;

0 commit comments

Comments
 (0)