RN: Remove Native Prop Validation

Summary:
As we migrate over to static typing solutions for props, we cannot rely on always having `propTypes` available at runtime.

This gets us started on that journey by removing the native prop validation that happens when we require native components.

bypass-lint

Reviewed By: TheSavior

Differential Revision: D7976854

fbshipit-source-id: f3ab579a7f0f8cfb716b0eb7fd4625f8168f3d96
This commit is contained in:
Tim Yung 2018-06-01 12:37:22 -07:00 committed by Facebook Github Bot
parent 6c910549d8
commit 8dc3ba0444
17 changed files with 84 additions and 288 deletions

View File

@ -11,7 +11,6 @@
'use strict';
const Platform = require('Platform');
const ProgressBarAndroid = require('ProgressBarAndroid');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');
@ -21,7 +20,10 @@ const requireNativeComponent = require('requireNativeComponent');
import type {NativeComponent} from 'ReactNative';
import type {ViewProps} from 'ViewPropTypes';
let RCTActivityIndicator;
const RCTActivityIndicator =
Platform.OS === 'android'
? require('ProgressBarAndroid')
: requireNativeComponent('RCTActivityIndicatorView');
const GRAY = '#999999';
@ -98,11 +100,7 @@ const ActivityIndicator = (
return (
<View onLayout={onLayout} style={[styles.container, style]}>
{Platform.OS === 'ios' ? (
<RCTActivityIndicator {...nativeProps} />
) : (
<ProgressBarAndroid {...nativeProps} />
)}
<RCTActivityIndicator {...nativeProps} />
</View>
);
};
@ -120,14 +118,6 @@ ActivityIndicatorWithRef.defaultProps = {
};
ActivityIndicatorWithRef.displayName = 'ActivityIndicator';
if (Platform.OS === 'ios') {
RCTActivityIndicator = requireNativeComponent(
'RCTActivityIndicatorView',
null,
{nativeOnly: {activityIndicatorViewStyle: true}},
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',

View File

@ -22,6 +22,8 @@ const requireNativeComponent = require('requireNativeComponent');
import type {ViewProps} from 'ViewPropTypes';
const RCTDatePickerIOS = requireNativeComponent('RCTDatePicker');
type Event = Object;
type Props = $ReadOnly<{|
@ -177,6 +179,4 @@ const styles = StyleSheet.create({
},
});
const RCTDatePickerIOS = requireNativeComponent('RCTDatePicker');
module.exports = DatePickerIOS;

View File

@ -17,6 +17,8 @@ const requireNativeComponent = require('requireNativeComponent');
import type {ViewProps} from 'ViewPropTypes';
const RCTMaskedView = requireNativeComponent('RCTMaskedView');
type Props = {
...ViewProps,
@ -97,12 +99,4 @@ class MaskedViewIOS extends React.Component<Props> {
}
}
const RCTMaskedView = requireNativeComponent('RCTMaskedView', {
name: 'RCTMaskedView',
displayName: 'RCTMaskedView',
propTypes: {
...ViewPropTypes,
},
});
module.exports = MaskedViewIOS;

View File

@ -25,6 +25,8 @@ import type {ImageSource} from 'ImageSource';
import type {ColorValue} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';
const RCTProgressView = requireNativeComponent('RCTProgressView');
type Props = $ReadOnly<{|
...ViewProps,
progressViewStyle?: ?('default' | 'bar'),
@ -91,11 +93,6 @@ const styles = StyleSheet.create({
},
});
const RCTProgressView = requireNativeComponent(
'RCTProgressView',
ProgressViewIOS,
);
module.exports = ((ProgressViewIOS: any): Class<
ReactNative.NativeComponent<Props>,
>);

View File

@ -14,6 +14,8 @@ const requireNativeComponent = require('requireNativeComponent');
import type {ViewProps} from 'ViewPropTypes';
const RCTSafeAreaView = requireNativeComponent('RCTSafeAreaView');
type Props = ViewProps & {
children: any,
};
@ -34,12 +36,4 @@ class SafeAreaView extends React.Component<Props> {
}
}
const RCTSafeAreaView = requireNativeComponent('RCTSafeAreaView', {
name: 'RCTSafeAreaView',
displayName: 'RCTSafeAreaView',
propTypes: {
...ViewPropTypes,
},
});
module.exports = SafeAreaView;

View File

@ -45,6 +45,28 @@ import type {PointProp} from 'PointPropType';
import type {ColorValue} from 'StyleSheetTypes';
let AndroidScrollView;
let AndroidHorizontalScrollContentView;
let AndroidHorizontalScrollView;
let RCTScrollView;
let RCTScrollContentView;
if (Platform.OS === 'android') {
AndroidScrollView = requireNativeComponent('RCTScrollView');
AndroidHorizontalScrollView = requireNativeComponent(
'AndroidHorizontalScrollView',
);
AndroidHorizontalScrollContentView = requireNativeComponent(
'AndroidHorizontalScrollContentView',
);
} else if (Platform.OS === 'ios') {
RCTScrollView = requireNativeComponent('RCTScrollView');
RCTScrollContentView = requireNativeComponent('RCTScrollContentView');
} else {
RCTScrollView = requireNativeComponent('RCTScrollView');
RCTScrollContentView = requireNativeComponent('RCTScrollContentView');
}
type TouchableProps = $ReadOnly<{|
onTouchStart?: (event: PressEvent) => void,
onTouchMove?: (event: PressEvent) => void,
@ -1074,56 +1096,4 @@ const styles = StyleSheet.create({
},
});
let nativeOnlyProps,
AndroidScrollView,
AndroidHorizontalScrollContentView,
AndroidHorizontalScrollView,
RCTScrollView,
RCTScrollContentView;
if (Platform.OS === 'android') {
nativeOnlyProps = {
nativeOnly: {
sendMomentumEvents: true,
},
};
AndroidScrollView = requireNativeComponent(
'RCTScrollView',
(ScrollView: React.ComponentType<any>),
nativeOnlyProps,
);
AndroidHorizontalScrollView = requireNativeComponent(
'AndroidHorizontalScrollView',
(ScrollView: React.ComponentType<any>),
nativeOnlyProps,
);
AndroidHorizontalScrollContentView = requireNativeComponent(
'AndroidHorizontalScrollContentView',
);
} else if (Platform.OS === 'ios') {
nativeOnlyProps = {
nativeOnly: {
onMomentumScrollBegin: true,
onMomentumScrollEnd: true,
onScrollBeginDrag: true,
onScrollEndDrag: true,
},
};
RCTScrollView = requireNativeComponent(
'RCTScrollView',
(ScrollView: React.ComponentType<any>),
nativeOnlyProps,
);
RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View);
} else {
nativeOnlyProps = {
nativeOnly: {},
};
RCTScrollView = requireNativeComponent(
'RCTScrollView',
null,
nativeOnlyProps,
);
RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View);
}
module.exports = TypedScrollView;

View File

@ -22,6 +22,8 @@ const requireNativeComponent = require('requireNativeComponent');
import type {ViewProps} from 'ViewPropTypes';
const RCTSegmentedControl = requireNativeComponent('RCTSegmentedControl');
type DefaultProps = {
values: $ReadOnlyArray<string>,
enabled: boolean,
@ -139,11 +141,6 @@ const styles = StyleSheet.create({
},
});
const RCTSegmentedControl = requireNativeComponent(
'RCTSegmentedControl',
SegmentedControlIOS,
);
module.exports = ((SegmentedControlIOS: any): Class<
ReactNative.NativeComponent<Props>,
>);

View File

@ -29,6 +29,8 @@ import type {ViewStyleProp} from 'StyleSheet';
import type {ColorValue} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';
const RCTSlider = requireNativeComponent('RCTSlider');
type Event = Object;
type IOSProps = $ReadOnly<{|
@ -306,14 +308,4 @@ if (Platform.OS === 'ios') {
});
}
let options = {};
if (Platform.OS === 'android') {
options = {
nativeOnly: {
enabled: true,
},
};
}
const RCTSlider = requireNativeComponent('RCTSlider', Slider, options);
module.exports = ((Slider: any): Class<ReactNative.NativeComponent<Props>>);

View File

@ -25,6 +25,11 @@ const requireNativeComponent = require('requireNativeComponent');
import type {ColorValue} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';
const RCTSwitch =
Platform.OS === 'android'
? requireNativeComponent('AndroidSwitch')
: requireNativeComponent('RCTSwitch');
type DefaultProps = $ReadOnly<{|
value: boolean,
disabled: boolean,
@ -40,6 +45,7 @@ type Props = $ReadOnly<{|
onTintColor?: ?ColorValue,
thumbTintColor?: ?ColorValue,
|}>;
/**
* Renders a boolean input.
*
@ -158,21 +164,4 @@ const styles = StyleSheet.create({
},
});
if (Platform.OS === 'android') {
var RCTSwitch = requireNativeComponent('AndroidSwitch', Switch, {
nativeOnly: {
onChange: true,
on: true,
enabled: true,
trackTintColor: true,
},
});
} else {
var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, {
nativeOnly: {
onChange: true,
},
});
}
module.exports = ((Switch: any): Class<ReactNative.NativeComponent<Props>>);

View File

@ -22,6 +22,8 @@ const requireNativeComponent = require('requireNativeComponent');
import type {DangerouslyImpreciseStyleProp} from 'StyleSheet';
import type {ViewProps} from 'ViewPropTypes';
const RCTTabBar = requireNativeComponent('RCTTabBar');
type Props = $ReadOnly<{|
...ViewProps,
style?: DangerouslyImpreciseStyleProp,
@ -102,6 +104,4 @@ const styles = StyleSheet.create({
},
});
const RCTTabBar = requireNativeComponent('RCTTabBar', TabBarIOS);
module.exports = TabBarIOS;

View File

@ -40,24 +40,22 @@ let AndroidTextInput;
let RCTMultilineTextInputView;
let RCTSinglelineTextInputView;
if (Platform.OS === 'android') {
AndroidTextInput = requireNativeComponent('AndroidTextInput');
} else if (Platform.OS === 'ios') {
RCTMultilineTextInputView = requireNativeComponent(
'RCTMultilineTextInputView',
);
RCTSinglelineTextInputView = requireNativeComponent(
'RCTSinglelineTextInputView',
);
}
const onlyMultiline = {
onTextInput: true,
children: true,
};
if (Platform.OS === 'android') {
AndroidTextInput = requireNativeComponent('AndroidTextInput', null);
} else if (Platform.OS === 'ios') {
RCTMultilineTextInputView = requireNativeComponent(
'RCTMultilineTextInputView',
null,
);
RCTSinglelineTextInputView = requireNativeComponent(
'RCTSinglelineTextInputView',
null,
);
}
type Event = Object;
type Selection = {
start: number,

View File

@ -10,11 +10,8 @@
'use strict';
const Platform = require('Platform');
const React = require('React');
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
const TextAncestor = require('TextAncestor');
const ViewPropTypes = require('ViewPropTypes');
const invariant = require('fbjs/lib/invariant');
const requireNativeComponent = require('requireNativeComponent');
@ -31,31 +28,7 @@ export type Props = ViewProps;
*
* @see http://facebook.github.io/react-native/docs/view.html
*/
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) {
if (!ViewPropTypes[prop] && !ReactNativeStyleAttributes[prop]) {
throw new Error(
'View is missing propType for native prop `' + prop + '`',
);
}
}
}
const RCTView = requireNativeComponent('RCTView');
let ViewToExport = RCTView;
if (__DEV__) {

View File

@ -23,6 +23,8 @@ const resolveAssetSource = require('resolveAssetSource');
const ImageViewManager = NativeModules.ImageViewManager;
const RCTImageView = requireNativeComponent('RCTImageView');
/**
* A React component for displaying different types of images,
* including network images, static resources, temporary local images, and
@ -139,6 +141,4 @@ const styles = StyleSheet.create({
},
});
const RCTImageView = requireNativeComponent('RCTImageView', Image);
module.exports = Image;

View File

@ -22,7 +22,9 @@ const View = require('View');
const deprecatedPropType = require('deprecatedPropType');
const requireNativeComponent = require('requireNativeComponent');
const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
const RCTModalHostView = requireNativeComponent('RCTModalHostView');
const ModalEventEmitter =
Platform.OS === 'ios' && NativeModules.ModalManager
? new NativeEventEmitter(NativeModules.ModalManager)

View File

@ -21,6 +21,13 @@ const ViewPropTypes = require('ViewPropTypes');
const requireNativeComponent = require('requireNativeComponent');
// Verify that RCTSnapshot is part of the UIManager since it is only loaded
// if you have linked against RCTTest like in tests, otherwise we will have
// a warning printed out
const RCTSnapshot = UIManager.RCTSnapshot
? requireNativeComponent('RCTSnapshot')
: View;
class SnapshotViewIOS extends React.Component<{
onSnapshotReady?: Function,
testIdentifier?: string,
@ -59,11 +66,4 @@ const style = StyleSheet.create({
},
});
// Verify that RCTSnapshot is part of the UIManager since it is only loaded
// if you have linked against RCTTest like in tests, otherwise we will have
// a warning printed out
const RCTSnapshot = UIManager.RCTSnapshot
? requireNativeComponent('RCTSnapshot', SnapshotViewIOS)
: View;
module.exports = SnapshotViewIOS;

View File

@ -10,7 +10,6 @@
'use strict';
const Platform = require('Platform');
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
const UIManager = require('UIManager');
@ -21,51 +20,25 @@ const pointsDiffer = require('pointsDiffer');
const processColor = require('processColor');
const resolveAssetSource = require('resolveAssetSource');
const sizesDiffer = require('sizesDiffer');
const verifyPropTypes = require('verifyPropTypes');
const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning');
type ComponentInterface =
| React$ComponentType<any>
| $ReadOnly<{
propTypes?: $ReadOnly<{
[propName: string]: mixed,
}>,
}>;
type ExtraOptions = $ReadOnly<{|
nativeOnly?: $ReadOnly<{
[propName: string]: boolean,
}>,
|}>;
/**
* Used to create React components that directly wrap native component
* implementations. Config information is extracted from data exported from the
* UIManager module. You should also wrap the native component in a
* hand-written component with full propTypes definitions and other
* documentation - pass the hand-written component in as `componentInterface` to
* verify all the native props are documented via `propTypes`.
* Creates values that can be used like React components which represent native
* view managers. You should create JavaScript modules that wrap these values so
* that the results are memoized. Example:
*
* If some native props shouldn't be exposed in the wrapper interface, you can
* pass null for `componentInterface` and call `verifyPropTypes` directly
* with `nativePropsToIgnore`;
* const View = requireNativeComponent('RCTView');
*
* Common types are lined up with the appropriate prop differs with
* `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`.
*/
const requireNativeComponent = (
viewName: string,
componentInterface?: ?ComponentInterface,
extraConfig?: ?ExtraOptions,
): string =>
createReactNativeComponentClass(viewName, () => {
const viewConfig = UIManager[viewName];
const requireNativeComponent = (uiViewClassName: string): string =>
createReactNativeComponentClass(uiViewClassName, () => {
const viewConfig = UIManager[uiViewClassName];
invariant(
viewConfig != null && viewConfig.NativeProps != null,
'requireNativeComponent: "%s" was not found in the UIManager.',
viewName,
uiViewClassName,
);
// TODO: This seems like a whole lot of runtime initialization for every
@ -94,14 +67,14 @@ const requireNativeComponent = (
}
}
const viewAttributes = {};
const validAttributes = {};
for (const key in nativeProps) {
const typeName = nativeProps[key];
const diff = getDifferForType(typeName);
const process = getProcessorForType(typeName);
viewAttributes[key] =
validAttributes[key] =
diff == null && process == null ? true : {diff, process};
}
@ -109,24 +82,15 @@ const requireNativeComponent = (
// props. This makes it so we allow style properties in the `style` prop.
// TODO: Move style properties into a `style` prop and disallow them as
// top-level props on the native side.
viewAttributes.style = ReactNativeStyleAttributes;
validAttributes.style = ReactNativeStyleAttributes;
Object.assign(viewConfig, {
uiViewClassName: viewName,
validAttributes: viewAttributes,
propTypes:
componentInterface == null ? null : componentInterface.propTypes,
uiViewClassName,
validAttributes,
bubblingEventTypes,
directEventTypes,
});
if (__DEV__) {
verifyPropTypes(
viewConfig,
extraConfig == null ? null : extraConfig.nativeOnly,
);
}
if (!hasAttachedDefaultEventTypes) {
attachDefaultEventTypes(viewConfig);
hasAttachedDefaultEventTypes = true;

View File

@ -1,64 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
function verifyPropTypes(
viewConfig: $ReadOnly<{
NativeProps: $ReadOnly<{
[propName: string]: mixed,
}>,
propTypes: ?$ReadOnly<{
[propName: string]: mixed,
}>,
uiViewClassName: string,
}>,
nativePropsToIgnore: ?$ReadOnly<{
[propName: string]: boolean,
}>,
) {
const {NativeProps, propTypes, uiViewClassName} = viewConfig;
if (propTypes == null) {
return;
}
for (const propName in NativeProps) {
if (
propTypes[propName] ||
ReactNativeStyleAttributes[propName] ||
(nativePropsToIgnore && nativePropsToIgnore[propName])
) {
continue;
}
const prettyName = `${uiViewClassName}.${propName}`;
const nativeType = String(NativeProps[propName]);
const suggestion =
'\n\nIf you have not changed this prop yourself, this usually means ' +
'that the versions of your native and JavaScript code are out of sync. ' +
'Updating both should make this error go away.';
if (propTypes.hasOwnProperty(propName)) {
console.error(
`Invalid propType to configure \`${prettyName}\` (${nativeType}).` +
suggestion,
);
} else {
console.error(
`Missing a propType to configure \`${prettyName}\` (${nativeType}).` +
suggestion,
);
}
}
}
module.exports = verifyPropTypes;