RN: Switch `View` to `React.forwardRef`

Reviewed By: bvaughn, sophiebits

Differential Revision: D7896711

fbshipit-source-id: c10c8a14a00ac2d67605e6e4fe1a341b4688fdd8
This commit is contained in:
Tim Yung 2018-05-09 00:47:48 -07:00 committed by Facebook Github Bot
parent e1339bc183
commit 3e534b9aab
4 changed files with 45 additions and 48 deletions

View File

@ -18,7 +18,7 @@ const invariant = require('fbjs/lib/invariant');
function createAnimatedComponent(Component: any): any { function createAnimatedComponent(Component: any): any {
invariant( invariant(
typeof Component === 'string' || typeof Component !== 'function' ||
(Component.prototype && Component.prototype.isReactComponent), (Component.prototype && Component.prototype.isReactComponent),
'`createAnimatedComponent` does not support stateless functional components; ' + '`createAnimatedComponent` does not support stateless functional components; ' +
'use a class component instead.', 'use a class component instead.',

View File

@ -4,23 +4,22 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
* *
* @flow
* @format * @format
* @flow
*/ */
'use strict'; 'use strict';
const Platform = require('Platform'); const Platform = require('Platform');
const React = require('React'); const React = require('React');
const ReactNative = require('ReactNative');
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
const TextAncestor = require('TextAncestor'); const TextAncestor = require('TextAncestor');
const ViewPropTypes = require('ViewPropTypes'); const ViewPropTypes = require('ViewPropTypes');
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
const requireNativeComponent = require('requireNativeComponent'); const requireNativeComponent = require('requireNativeComponent');
import type {NativeComponent} from 'ReactNative';
import type {ViewProps} from 'ViewPropTypes'; import type {ViewProps} from 'ViewPropTypes';
export type Props = ViewProps; export type Props = ViewProps;
@ -32,50 +31,25 @@ export type Props = ViewProps;
* *
* @see http://facebook.github.io/react-native/docs/view.html * @see http://facebook.github.io/react-native/docs/view.html
*/ */
class View extends ReactNative.NativeComponent<Props> { const RCTView = requireNativeComponent(
static propTypes = ViewPropTypes; 'RCTView',
{
viewConfig = { propTypes: ViewPropTypes,
uiViewClassName: 'RCTView',
validAttributes: ReactNativeViewAttributes.RCTView,
};
/**
* WARNING: This method will not be used in production mode as in that mode we
* replace wrapper component View with generated native wrapper RCTView. Avoid
* adding functionality this component that you'd want to be available in both
* dev and prod modes.
*/
render() {
return (
<TextAncestor.Consumer>
{hasTextAncestor => {
// TODO: Change iOS to behave the same as Android.
invariant(
!hasTextAncestor || Platform.OS !== 'android',
'Nesting of <View> within <Text> is not supported on Android.',
);
return <RCTView {...this.props} />;
}}
</TextAncestor.Consumer>
);
}
}
const RCTView = requireNativeComponent('RCTView', View, {
nativeOnly: {
nativeBackgroundAndroid: true,
nativeForegroundAndroid: true,
}, },
}); {
nativeOnly: {
nativeBackgroundAndroid: true,
nativeForegroundAndroid: true,
},
},
);
if (__DEV__) { if (__DEV__) {
const UIManager = require('UIManager'); const UIManager = require('UIManager');
const viewConfig = const viewConfig =
(UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {}; (UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {};
for (const prop in viewConfig.nativeProps) { for (const prop in viewConfig.nativeProps) {
const viewAny: any = View; // Appease flow if (!ViewPropTypes[prop] && !ReactNativeStyleAttributes[prop]) {
if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) {
throw new Error( throw new Error(
'View is missing propType for native prop `' + prop + '`', 'View is missing propType for native prop `' + prop + '`',
); );
@ -85,8 +59,20 @@ if (__DEV__) {
let ViewToExport = RCTView; let ViewToExport = RCTView;
if (__DEV__) { if (__DEV__) {
ViewToExport = View; // $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet.
ViewToExport = React.forwardRef((props, ref) => (
<TextAncestor.Consumer>
{hasTextAncestor => {
// TODO: Change iOS to behave the same as Android.
invariant(
!hasTextAncestor || Platform.OS !== 'android',
'Nesting of <View> within <Text> is not supported on Android.',
);
return <RCTView {...props} ref={ref} />;
}}
</TextAncestor.Consumer>
));
ViewToExport.displayName = 'View';
} }
// No one should depend on the DEV-mode createClass View wrapper. module.exports = ((ViewToExport: any): Class<NativeComponent<ViewProps, any>>);
module.exports = ((ViewToExport: any): typeof View);

View File

@ -3,23 +3,34 @@
* *
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*
* @format
*/ */
'use strict'; 'use strict';
module.exports = moduleName => { module.exports = (moduleName, instanceMethods) => {
const RealComponent = require.requireActual(moduleName); const RealComponent = require.requireActual(moduleName);
const React = require('react'); const React = require('react');
const Component = class extends RealComponent { const SuperClass =
typeof RealComponent === 'function' ? RealComponent : React.Component;
const Component = class extends SuperClass {
render() { render() {
const name = RealComponent.displayName || RealComponent.name; const name = RealComponent.displayName || RealComponent.name;
return React.createElement( return React.createElement(
name.replace(/^(RCT|RK)/,''), name.replace(/^(RCT|RK)/, ''),
this.props, this.props,
this.props.children, this.props.children,
); );
} }
}; };
if (instanceMethods != null) {
Object.assign(Component.prototype, instanceMethods);
}
return Component; return Component;
}; };

View File

@ -38,7 +38,7 @@ jest
.mock('Text', () => mockComponent('Text')) .mock('Text', () => mockComponent('Text'))
.mock('TextInput', () => mockComponent('TextInput')) .mock('TextInput', () => mockComponent('TextInput'))
.mock('Modal', () => mockComponent('Modal')) .mock('Modal', () => mockComponent('Modal'))
.mock('View', () => mockComponent('View')) .mock('View', () => mockComponent('View', MockNativeMethods))
.mock('RefreshControl', () => require.requireMock('RefreshControlMock')) .mock('RefreshControl', () => require.requireMock('RefreshControlMock'))
.mock('ScrollView', () => require.requireMock('ScrollViewMock')) .mock('ScrollView', () => require.requireMock('ScrollViewMock'))
.mock( .mock(