[ReactNative] verifyPropTypes against native exports

This commit is contained in:
Spencer Ahrens 2015-04-16 18:17:19 -07:00
Родитель 764854c04a
Коммит 915151c5d7
10 изменённых файлов: 177 добавлений и 64 удалений

Просмотреть файл

@ -226,10 +226,8 @@ exports.examples = [
Contain
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.contain}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.contain}
source={fullImage}
/>
</View>
@ -238,10 +236,8 @@ exports.examples = [
Cover
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.cover}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.cover}
source={fullImage}
/>
</View>
@ -250,10 +246,8 @@ exports.examples = [
Stretch
</Text>
<Image
style={[
styles.resizeMode,
{resizeMode: Image.resizeMode.stretch}
]}
style={styles.resizeMode}
resizeMode={Image.resizeMode.stretch}
source={fullImage}
/>
</View>

Просмотреть файл

@ -17,7 +17,6 @@ var PointPropType = require('PointPropType');
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
var RCTScrollViewConsts = RCTScrollView.Constants;
var React = require('React');
var ReactIOSTagHandles = require('ReactIOSTagHandles');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var RCTUIManager = require('NativeModules').UIManager;
var ScrollResponder = require('ScrollResponder');
@ -32,6 +31,7 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var pointsDiffer = require('pointsDiffer');
var requireNativeComponent = require('requireNativeComponent');
var PropTypes = React.PropTypes;
@ -73,6 +73,12 @@ var ScrollView = React.createClass({
* the `alwaysBounce*` props are true. The default value is true.
*/
bounces: PropTypes.bool,
/**
* When true, gestures can drive zoom past min/max and the zoom will animate
* to the min/max value at gesture end, otherwise the zoom will not exceed
* the limits.
*/
bouncesZoom: PropTypes.bool,
/**
* When true, the scroll view bounces horizontally when it reaches the end
* even if the content is smaller than the scroll view itself. The default
@ -120,6 +126,16 @@ var ScrollView = React.createClass({
* instead of vertically in a column. The default value is false.
*/
horizontal: PropTypes.bool,
/**
* When true, the ScrollView will try to lock to only vertical or horizontal
* scrolling while dragging. The default value is false.
*/
directionalLockEnabled: PropTypes.bool,
/**
* When false, once tracking starts, won't try to drag if the touch moves.
* The default value is true.
*/
canCancelContentTouches: PropTypes.bool,
/**
* Determines whether the keyboard gets dismissed in response to a drag.
* - 'none' (the default), drags do not dismiss the keyboard.
@ -359,6 +375,7 @@ if (Platform.OS === 'android') {
validAttributes: validAttributes,
uiViewClassName: 'RCTScrollView',
});
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
}
module.exports = ScrollView;

Просмотреть файл

@ -12,6 +12,7 @@
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
@ -21,6 +22,7 @@ var View = require('View');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
@ -96,16 +98,20 @@ var styles = StyleSheet.create({
},
});
var validAttributes = {
...ReactIOSViewAttributes.UIView,
value: true,
minimumValue: true,
maximumValue: true,
};
if (Platform.OS === 'ios') {
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
} else {
var validAttributes = {
...ReactIOSViewAttributes.UIView,
value: true,
minimumValue: true,
maximumValue: true,
};
var RCTSlider = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTSlider',
});
var RCTSlider = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'RCTSlider',
});
}
module.exports = SliderIOS;

Просмотреть файл

@ -14,6 +14,7 @@
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
@ -27,7 +28,9 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var warning = require('warning');
var verifyPropTypes = require('verifyPropTypes');
/**
* A react component for displaying different types of images,
@ -64,6 +67,13 @@ var Image = React.createClass({
source: PropTypes.shape({
uri: PropTypes.string,
}),
/**
* A static image to display while downloading the final image off the
* network.
*/
defaultSource: PropTypes.shape({
uri: PropTypes.string,
}),
/**
* Whether this element should be revealed as an accessible element.
*/
@ -80,6 +90,11 @@ var Image = React.createClass({
* [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets)
*/
capInsets: EdgeInsetsPropType,
/**
* Determines how to resize the image when the frame doesn't match the raw
* image dimensions.
*/
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
style: StyleSheetPropType(ImageStylePropTypes),
/**
* A unique identifier for this element to be used in UI Automation
@ -104,6 +119,12 @@ var Image = React.createClass({
},
render: function() {
for (var prop in nativeOnlyProps) {
if (this.props[prop] !== undefined) {
console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
'not be set directly on Image.');
}
}
var style = flattenStyle([styles.base, this.props.style]);
invariant(style, "style must be initialized");
var source = this.props.source;
@ -119,28 +140,36 @@ var Image = React.createClass({
if (this.props.style && this.props.style.tintColor) {
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
}
var resizeMode = this.props.resizeMode || style.resizeMode;
var contentModes = NativeModules.UIManager.UIView.ContentMode;
var resizeMode;
if (style.resizeMode === ImageResizeMode.stretch) {
resizeMode = contentModes.ScaleToFill;
} else if (style.resizeMode === ImageResizeMode.contain) {
resizeMode = contentModes.ScaleAspectFit;
var contentMode;
if (resizeMode === ImageResizeMode.stretch) {
contentMode = contentModes.ScaleToFill;
} else if (resizeMode === ImageResizeMode.contain) {
contentMode = contentModes.ScaleAspectFit;
} else { // ImageResizeMode.cover or undefined
resizeMode = contentModes.ScaleAspectFill;
contentMode = contentModes.ScaleAspectFill;
}
var nativeProps = merge(this.props, {
style,
resizeMode,
contentMode,
tintColor: style.tintColor,
});
if (Platform.OS === 'android') {
// TODO: update android native code to not need this
nativeProps.resizeMode = contentMode;
delete nativeProps.contentMode;
}
if (isStored) {
nativeProps.imageTag = source.uri;
} else {
nativeProps.src = source.uri;
}
if (this.props.defaultSource) {
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
}
return <RawImage {...nativeProps} />;
}
});
@ -151,24 +180,39 @@ var styles = StyleSheet.create({
},
});
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
accessible: true,
accessibilityLabel: true,
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
imageTag: true,
resizeMode: true,
if (Platform.OS === 'android') {
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
accessible: true,
accessibilityLabel: true,
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
imageTag: true,
resizeMode: true,
src: true,
testID: PropTypes.string,
});
var RCTStaticImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
uiViewClassName: 'RCTStaticImage',
});
var RCTNetworkImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
uiViewClassName: 'RCTNetworkImageView',
});
} else {
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
}
var nativeOnlyProps = {
src: true,
testID: PropTypes.string,
});
var RCTStaticImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
uiViewClassName: 'RCTStaticImage',
});
var RCTNetworkImage = createReactIOSNativeComponentClass({
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
uiViewClassName: 'RCTNetworkImageView',
});
defaultImageSrc: true,
imageTag: true,
contentMode: true,
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
}
module.exports = Image;

Просмотреть файл

@ -29,6 +29,6 @@ RCT_EXPORT_MODULE()
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
@end

Просмотреть файл

@ -26,7 +26,7 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
{
if (json) {

Просмотреть файл

@ -10,22 +10,23 @@
* @flow
*/
"use strict";
'use strict';
var ImageStylePropTypes = require('ImageStylePropTypes');
var TextStylePropTypes = require('TextStylePropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
var deepDiffer = require('deepDiffer');
var keyMirror = require('keyMirror');
var matricesDiffer = require('matricesDiffer');
var merge = require('merge');
var sizesDiffer = require('sizesDiffer');
var ReactIOSStyleAttributes = merge(
keyMirror(ViewStylePropTypes),
keyMirror(TextStylePropTypes)
);
var ReactIOSStyleAttributes = {
...keyMirror(ViewStylePropTypes),
...keyMirror(TextStylePropTypes),
...keyMirror(ImageStylePropTypes),
};
ReactIOSStyleAttributes.transformMatrix = { diff: matricesDiffer };
ReactIOSStyleAttributes.shadowOffset = { diff: deepDiffer };
ReactIOSStyleAttributes.shadowOffset = { diff: sizesDiffer };
module.exports = ReactIOSStyleAttributes;

Просмотреть файл

@ -20,18 +20,27 @@ var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
/**
* Used to create React components that directly wrap native component
* implementations. Config information is extracted from data exported from the
* RCTUIManager module. It is still strongly preferred that you wrap the native
* component in a hand-written component with full propTypes definitions and
* other documentation.
* RCTUIManager 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 `wrapperComponent` to
* verify all the native props are documented via `propTypes`.
*
* If some native props shouldn't be exposed in the wrapper interface, you can
* pass null for `wrapperComponent` and call `verifyPropTypes` directly
* with `nativePropsToIgnore`;
*
* Common types are lined up with the appropriate prop differs with
* `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`.
*/
function requireNativeComponent(viewName: string): Function {
function requireNativeComponent(
viewName: string,
wrapperComponent: ?Function
): Function {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
if (!viewConfig) {
return UnimplementedView;
@ -46,6 +55,9 @@ function requireNativeComponent(viewName: string): Function {
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
viewConfig.validAttributes[key] = {diff: differ};
}
if (__DEV__) {
wrapperComponent && verifyPropTypes(wrapperComponent, viewConfig);
}
return createReactIOSNativeComponentClass(viewConfig);
}

Просмотреть файл

@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule verifyPropTypes
* @flow
*/
'use strict';
var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes');
var View = require('View');
function verifyPropTypes(
component: Function,
viewConfig: Object,
nativePropsToIgnore?: Object
) {
if (!viewConfig) {
return; // This happens for UnimplementedView.
}
var nativeProps = viewConfig.nativeProps;
for (var prop in viewConfig.nativeProps) {
if (!component.propTypes[prop] &&
!View.propTypes[prop] &&
!ReactIOSStyleAttributes[prop] &&
(!nativePropsToIgnore || !nativePropsToIgnore[prop])) {
throw new Error(
'`' + component.displayName + '` has no propType for native prop `' +
viewConfig.uiViewClassName + '.' + prop + '` of native type `' +
nativeProps[prop].type + '`'
);
}
}
}
module.exports = verifyPropTypes;

Просмотреть файл

@ -61,7 +61,6 @@ RCT_EXPORT_MODULE()
#pragma mark - View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString)
RCT_EXPORT_VIEW_PROPERTY(hidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)