From 3e534b9aab5156adac67762877b2457408fe8934 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 9 May 2018 00:47:48 -0700 Subject: [PATCH] RN: Switch `View` to `React.forwardRef` Reviewed By: bvaughn, sophiebits Differential Revision: D7896711 fbshipit-source-id: c10c8a14a00ac2d67605e6e4fe1a341b4688fdd8 --- .../Animated/src/createAnimatedComponent.js | 2 +- Libraries/Components/View/View.js | 72 ++++++++----------- jest/mockComponent.js | 17 ++++- jest/setup.js | 2 +- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js index d876af645..a4177d65b 100644 --- a/Libraries/Animated/src/createAnimatedComponent.js +++ b/Libraries/Animated/src/createAnimatedComponent.js @@ -18,7 +18,7 @@ const invariant = require('fbjs/lib/invariant'); function createAnimatedComponent(Component: any): any { invariant( - typeof Component === 'string' || + typeof Component !== 'function' || (Component.prototype && Component.prototype.isReactComponent), '`createAnimatedComponent` does not support stateless functional components; ' + 'use a class component instead.', diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 1e79f88a5..337f77d1a 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -4,23 +4,22 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow * @format + * @flow */ 'use strict'; const Platform = require('Platform'); const React = require('React'); -const ReactNative = require('ReactNative'); const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); -const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const TextAncestor = require('TextAncestor'); const ViewPropTypes = require('ViewPropTypes'); const invariant = require('fbjs/lib/invariant'); const requireNativeComponent = require('requireNativeComponent'); +import type {NativeComponent} from 'ReactNative'; import type {ViewProps} from 'ViewPropTypes'; export type Props = ViewProps; @@ -32,50 +31,25 @@ export type Props = ViewProps; * * @see http://facebook.github.io/react-native/docs/view.html */ -class View extends ReactNative.NativeComponent { - static propTypes = ViewPropTypes; - - viewConfig = { - 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 ( - - {hasTextAncestor => { - // TODO: Change iOS to behave the same as Android. - invariant( - !hasTextAncestor || Platform.OS !== 'android', - 'Nesting of within is not supported on Android.', - ); - return ; - }} - - ); - } -} - -const RCTView = requireNativeComponent('RCTView', View, { - nativeOnly: { - nativeBackgroundAndroid: true, - nativeForegroundAndroid: true, +const RCTView = requireNativeComponent( + 'RCTView', + { + propTypes: ViewPropTypes, }, -}); + { + nativeOnly: { + nativeBackgroundAndroid: true, + nativeForegroundAndroid: true, + }, + }, +); if (__DEV__) { const UIManager = require('UIManager'); const viewConfig = (UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {}; for (const prop in viewConfig.nativeProps) { - const viewAny: any = View; // Appease flow - if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) { + if (!ViewPropTypes[prop] && !ReactNativeStyleAttributes[prop]) { throw new Error( 'View is missing propType for native prop `' + prop + '`', ); @@ -85,8 +59,20 @@ if (__DEV__) { let ViewToExport = RCTView; if (__DEV__) { - ViewToExport = View; + // $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. + ViewToExport = React.forwardRef((props, ref) => ( + + {hasTextAncestor => { + // TODO: Change iOS to behave the same as Android. + invariant( + !hasTextAncestor || Platform.OS !== 'android', + 'Nesting of within is not supported on Android.', + ); + return ; + }} + + )); + ViewToExport.displayName = 'View'; } -// No one should depend on the DEV-mode createClass View wrapper. -module.exports = ((ViewToExport: any): typeof View); +module.exports = ((ViewToExport: any): Class>); diff --git a/jest/mockComponent.js b/jest/mockComponent.js index 84c162d86..bdc16d805 100644 --- a/jest/mockComponent.js +++ b/jest/mockComponent.js @@ -3,23 +3,34 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @format */ + 'use strict'; -module.exports = moduleName => { +module.exports = (moduleName, instanceMethods) => { const RealComponent = require.requireActual(moduleName); const React = require('react'); - const Component = class extends RealComponent { + const SuperClass = + typeof RealComponent === 'function' ? RealComponent : React.Component; + + const Component = class extends SuperClass { render() { const name = RealComponent.displayName || RealComponent.name; return React.createElement( - name.replace(/^(RCT|RK)/,''), + name.replace(/^(RCT|RK)/, ''), this.props, this.props.children, ); } }; + + if (instanceMethods != null) { + Object.assign(Component.prototype, instanceMethods); + } + return Component; }; diff --git a/jest/setup.js b/jest/setup.js index b40bcebae..2aef9f1b3 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -38,7 +38,7 @@ jest .mock('Text', () => mockComponent('Text')) .mock('TextInput', () => mockComponent('TextInput')) .mock('Modal', () => mockComponent('Modal')) - .mock('View', () => mockComponent('View')) + .mock('View', () => mockComponent('View', MockNativeMethods)) .mock('RefreshControl', () => require.requireMock('RefreshControlMock')) .mock('ScrollView', () => require.requireMock('ScrollViewMock')) .mock(