[ReactNative] introduce requireNativeComponent

This commit is contained in:
Spencer Ahrens 2015-04-16 17:14:11 -07:00
parent baed197a7d
commit 764854c04a
6 changed files with 165 additions and 2 deletions

View File

@ -0,0 +1,37 @@
/**
* Common implementation for a simple stubbed view. Simply applies the view's styles to the inner
* View component and renders its children.
*
* @providesModule UnimplementedView
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');
var UnimplementedView = React.createClass({
setNativeProps: function() {
// Do nothing.
// This method is required in order to use this view as a Touchable* child.
// See ensureComponentIsNative.js for more info
},
render: function() {
return (
<View style={[styles.unimplementedView, this.props.style]}>
{this.props.children}
</View>
);
},
});
var styles = StyleSheet.create({
unimplementedView: {
borderWidth: 1,
borderColor: 'red',
alignSelf: 'flex-start',
}
});
module.exports = UnimplementedView;

View File

@ -0,0 +1,62 @@
/**
* 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 requireNativeComponent
* @flow
*/
'use strict';
var RCTUIManager = require('NativeModules').UIManager;
var UnimplementedView = require('UnimplementedView');
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
var deepDiffer = require('deepDiffer');
var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
/**
* 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.
*
* 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 {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
if (!viewConfig) {
return UnimplementedView;
}
var nativeProps = {
...RCTUIManager.viewConfigs.RCTView.nativeProps,
...viewConfig.nativeProps,
};
viewConfig.validAttributes = {};
for (var key in nativeProps) {
// TODO: deep diff by default in diffRawProperties instead of setting it here
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
viewConfig.validAttributes[key] = {diff: differ};
}
return createReactIOSNativeComponentClass(viewConfig);
}
var TypeToDifferMap = {
// iOS Types
CATransform3D: matricesDiffer,
CGPoint: pointsDiffer,
CGSize: sizesDiffer,
UIEdgeInsets: insetsDiffer,
// Android Types
// (not yet implemented)
};
module.exports = requireNativeComponent;

View File

@ -0,0 +1,19 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule sizesDiffer
*/
'use strict';
var dummySize = {w: undefined, h: undefined};
var sizesDiffer = function(one, two) {
one = one || dummySize;
two = two || dummySize;
return one !== two && (
one.w !== two.w ||
one.h !== two.h
);
};
module.exports = sizesDiffer;

View File

@ -59,6 +59,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
// Plugins
DeviceEventEmitter: require('RCTDeviceEventEmitter'),
NativeModules: require('NativeModules'),
requireNativeComponent: require('requireNativeComponent'),
addons: {
LinkedStateMixin: require('LinkedStateMixin'),

View File

@ -193,6 +193,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
NSMutableDictionary *_defaultShadowViews; // RCT thread only
NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers;
NSDictionary *_viewConfigs;
NSUInteger _rootTag;
}
@ -219,6 +220,28 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
return name;
}
static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewName)
{
NSMutableDictionary *nativeProps = [[NSMutableDictionary alloc] init];
static const char *prefix = "getPropConfig";
static const NSUInteger prefixLength = sizeof("getPropConfig") - 1;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(objc_getMetaClass(class_getName(managerClass)), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL getInfo = method_getName(method);
const char *selName = sel_getName(getInfo);
if (strlen(selName) > prefixLength && strncmp(selName, prefix, prefixLength) == 0) {
NSDictionary *info = ((NSDictionary *(*)(id, SEL))method_getImplementation(method))(managerClass, getInfo);
nativeProps[info[@"name"]] = info;
}
}
return @{
@"uiViewClassName": viewName,
@"nativeProps": nativeProps
};
}
/**
* This private constructor should only be called when creating
* isolated UIImanager instances for testing. Normal initialization
@ -292,13 +315,17 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
NSMutableDictionary *viewConfigs = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
NSString *viewName = RCTViewNameForModuleName(moduleName);
viewManagers[viewName] = manager;
viewConfigs[viewName] = RCTViewConfigForModule([manager class], viewName);
}
}];
_viewManagers = [viewManagers copy];
_viewConfigs = [viewConfigs copy];
}
- (void)registerRootView:(UIView *)rootView;
@ -1374,7 +1401,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
allJSConstants[name] = [constantsNamespace copy];
}
}];
allJSConstants[@"viewConfigs"] = _viewConfigs;
return allJSConstants;
}

View File

@ -109,6 +109,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* within the view or shadowView.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
@ -117,6 +118,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
}
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forShadowView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keyPath, @selector(type:), json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keyPath))) { \
@ -130,9 +132,11 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* refer to "json", "view" and "defaultView" to implement the required logic.
*/
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
/**
@ -160,4 +164,17 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
[self set_##newName:json forView:view withDefaultView:defaultView]; \
}
/**
* PROP_CONFIG macros should only be paired with property setters.
*/
#define RCT_EXPORT_VIEW_PROP_CONFIG(name, type) \
+ (NSDictionary *)getPropConfigView_##name { \
return @{@"name": @#name, @"type": @#type}; \
}
#define RCT_EXPORT_SHADOW_PROP_CONFIG(name, type) \
+ (NSDictionary *)getPropConfigShadow_##name { \
return @{@"name": @#name, @"type": @#type}; \
}
@end