阿里云服务器怎么建网站,卡盟网站怎么做图片素材,内网网站建设工作会议,域名信息查询网站在如今的App中#xff0c;已经有成千上万的原生UI部件了——其中的一些是平台的一部分#xff0c;另一些可能来自于一些第三方库#xff0c;而且可能你自己还收藏了很多。React Native已经封装了大部分最常见的组件#xff0c;譬如ScrollView和TextInput#xff0c;但不可… 在如今的App中已经有成千上万的原生UI部件了——其中的一些是平台的一部分另一些可能来自于一些第三方库而且可能你自己还收藏了很多。React Native已经封装了大部分最常见的组件譬如ScrollView和TextInput但不可能封装全部组件。而且说不定你曾经为自己以前的App还封装过一些组件React Native肯定没法包含它们。幸运的是在React Naitve应用程序中封装和植入已有的组件非常简单。 和原生模块向导一样本向导也是一个相对高级的向导我们假设你已经对iOS编程颇有经验。本向导会引导你如何构建一个原生UI组件带领你了解React Native核心库中MapView组件的具体实现。 iOS MapView样例 假设我们要把地图组件植入到我们的App中——我们用到的是MKMapView而现在只需要让它可以被Javascript重用。 原生视图都需要被一个RCTViewManager的子类来创建和管理。这些管理器在功能上有些类似“视图控制器”但它们本质上都是单例 - React Native只会为每个管理器创建一个实例。它们创建原生的视图并提供给RCTUIManagerRCTUIManager则会反过来委托它们在需要的时候去设置和更新视图的属性。RCTViewManager还会代理视图的所有委托并给JavaScript发回对应的事件。 提供原生视图很简单 首先创建一个子类添加RCT_EXPORT_MODULE()标记宏实现-(UIView *)view方法 // RCTMapManager.m
#import MapKit/MapKit.h#import RCTViewManager.hinterface RCTMapManager : RCTViewManager
endimplementation RCTMapManagerRCT_EXPORT_MODULE()- (UIView *)view
{return [[MKMapView alloc] init];
}end接下来你需要一些Javascript代码来让这个视图变成一个可用的React组件 // MapView.jsvar { requireNativeComponent } require(react-native);// requireNativeComponent 自动把这个组件提供给 RCTMapManager
module.exports requireNativeComponent(RCTMap, null);现在我们就已经实现了一个完整功能的地图组件了诸如捏放和其它的手势都已经完整支持。但是现在我们还不能真正的从Javascript端控制它。(╯﹏╰) 属性 我们能让这个组件变得更强大的第一件事情就是要能够封装一些原生属性供Javascript使用。举例来说我们希望能够禁用手指捏放操作然后指定一个初始的地图可见区域。禁用捏放操作只需要一个布尔值类型的属性就行了所以我们添加这么一行 // RCTMapManager.m
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)注意我们现在把类型声明为BOOL类型——React Native用RCTConvert来在JavaScript和原生代码之间完成类型转换。如果转换无法完成会产生一个“红屏”的报错提示这样你就能立即知道代码中出现了问题。如果一切进展顺利上面这个宏就已经包含了导出属性的全部实现。 现在要想禁用捏放操作我们只需要在JS里设置对应的属性 // MyApp.js
MapView pitchEnabled{false} /但这样并不能很好的说明这个组件的用法——用户要想知道我们的组件有哪些属性可以用以及可以取什么样的值他不得不一路翻到Objective-C的代码。要解决这个问题我们可以创建一个封装组件并且通过PropTypes来说明这个组件的接口。 // MapView.js
var React require(react-native);
var { requireNativeComponent } React;class MapView extends React.Component {render() {return RCTMap {...this.props} /;}
}MapView.propTypes {/*** 当这个属性被设置为true并且地图上绑定了一个有效的可视区域的情况下* 可以通过捏放操作来改变摄像头的偏转角度。* 当这个属性被设置成false时摄像头的角度会被忽略地图会一直显示为俯视状态。*/pitchEnabled: React.PropTypes.bool,
};var RCTMap requireNativeComponent(RCTMap, MapView);module.exports MapView;译注使用了封装组件之后你还需要注意到module.exports导出的不再是requireNativeComponent的返回值而是所创建的包装组件。 现在我们有了一个封装好的组件还有了一些注释文档用户使用起来也更方便了。注意我们现在把requireNativeComponent的第二个参数从null变成了用于封装的组件MapView。这使得React Native的底层框架可以检查原生属性和包装类的属性是否一致来减少出现问题的可能。 现在让我们添加一个更复杂些的region属性。我们首先添加原生代码 // RCTMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
{[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}这段代码比刚才的一个简单的BOOL要复杂的多了。现在我们多了一个需要做类型转换的MKCoordinateRegion类型还添加了一部分自定义的代码这样当我们在JS里改变地图的可视区域的时候视角会平滑地移动过去。在我们提供的函数体内json代表了JS中传递的尚未解析的原始值。函数里还有一个view变量使得我们可以访问到对应的视图实例。最后还有一个defaultView对象这样当JS给我们发送null的时候可以把视图的这个属性重置回默认值。 你可以为视图编写任何你所需要的转换函数——下面就是MKCoordinateRegion的转换实现它通过两个RCTConvert的扩展来完成 implementation RCTConvert(CoreLocation)RCT_CONVERTER(CLLocationDegrees, CLLocationDegrees, doubleValue);
RCT_CONVERTER(CLLocationDistance, CLLocationDistance, doubleValue); (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json
{json [self NSDictionary:json];return (CLLocationCoordinate2D){[self CLLocationDegrees:json[latitude]],[self CLLocationDegrees:json[longitude]]};
}endimplementation RCTConvert(MapKit) (MKCoordinateSpan)MKCoordinateSpan:(id)json
{json [self NSDictionary:json];return (MKCoordinateSpan){[self CLLocationDegrees:json[latitudeDelta]],[self CLLocationDegrees:json[longitudeDelta]]};
} (MKCoordinateRegion)MKCoordinateRegion:(id)json
{return (MKCoordinateRegion){[self CLLocationCoordinate2D:json],[self MKCoordinateSpan:json]};
}这些转换函数被设计为可以安全的处理任何JS扔过来的JSON当有任何缺少的键或者其它问题发生的时候显示一个“红屏”的错误提示。 为了完成region属性的支持我们还需要在propTypes里添加相应的说明否则我们会立刻收到一个错误提示然后就可以像使用其他属性一样使用了 // MapView.jsMapView.propTypes {/*** 当这个属性被设置为true并且地图上绑定了一个有效的可视区域的情况下* 可以通过捏放操作来改变摄像头的偏转角度。* 当这个属性被设置成false时摄像头的角度会被忽略地图会一直显示为俯视状态。*/pitchEnabled: React.PropTypes.bool,/*** 地图要显示的区域。** 区域由中心点坐标和区域范围坐标来定义。* */region: React.PropTypes.shape({/*** 地图中心点的坐标。*/latitude: React.PropTypes.number.isRequired,longitude: React.PropTypes.number.isRequired,/*** 最小/最大经、纬度间的距离。*/latitudeDelta: React.PropTypes.number.isRequired,longitudeDelta: React.PropTypes.number.isRequired,}),
};// MyApp.jsrender() {var region {latitude: 37.48,longitude: -122.16,latitudeDelta: 0.1,longitudeDelta: 0.1,};return MapView region{region} /;}现在你可以看到region属性的整个结构已经加上了文档说明——将来可能我们会自动生成一些类似的代码但目前还没有这样的手段。 有时候你的原生组件有一些特殊的属性希望导出但并不希望它成为公开的接口。举个例子Switch组件可能会有一个onChange属性用来传递原始的原生事件然后导出一个onValueChange属性这个属性在调用的时候会带上Switch的状态作为参数之一。这样的话你可能不希望原生专用的属性出现在API之中也就不希望把它放到propTypes里。可是如果你不放的话又会出现一个报错。解决方案就是带上额外的nativeOnly参数像这样 var RCTSwitch requireNativeComponent(RCTSwitch, Switch, {nativeOnly: { onChange: true }
});事件 现在我们已经有了一个原生地图组件并且从JS可以很容易的控制它了。不过我们怎么才能处理来自用户的事件譬如缩放操作或者拖动来改变可视区域关键的步骤就在于让RCTMapManager来委托我们提供的所有视图然后把事件通过分发器传递给JavaScript。最终的代码看起来类似这样比起完整的实现有所简化 // RCTMapManager.m#import RCTMapManager.h#import MapKit/MapKit.h#import RCTBridge.h
#import RCTEventDispatcher.h
#import UIViewReact.hinterface RCTMapManager() MKMapViewDelegate
endimplementation RCTMapManagerRCT_EXPORT_MODULE()- (UIView *)view
{MKMapView *map [[MKMapView alloc] init];map.delegate self;return map;
}#pragma mark MKMapViewDelegate- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated
{MKCoordinateRegion region mapView.region;NSDictionary *event {target: mapView.reactTag,region: {latitude: (region.center.latitude),longitude: (region.center.longitude),latitudeDelta: (region.span.latitudeDelta),longitudeDelta: (region.span.longitudeDelta),}};[self.bridge.eventDispatcher sendInputEventWithName:topChange body:event];
}如你所见我们刚才配置了管理器委托它代理创建的所有视图并且在委托方法-mapView:regionDidChangeAnimated:中把地图目前的区域以及reactTag目标封装成了一个事件这样我们的事件就可以通过sendInputEventWithName:body:发送到正确的React组件实例上。事件名topChange对应的是JavaScript端的onChange回调属性。这个回调会被原生事件执行然后我们通常都会在封装组件里做一些处理来使得API更简明 // MapView.jsclass MapView extends React.Component {constructor() {this._onChange this._onChange.bind(this);}_onChange(event: Event) {if (!this.props.onRegionChange) {return;}this.props.onRegionChange(event.nativeEvent.region);}render() {return RCTMap {...this.props} onChange{this._onChange} /;}
}
MapView.propTypes {/*** Callback that is called continuously when the user is dragging the map.*/onRegionChange: React.PropTypes.func,...
};样式 因为我们所有的视图都是UIView的子类大部分的样式属性应该直接就可以生效。但有一部分组件会希望使用自己定义的默认样式例如UIDatePicker希望自己的大小是固定的。这个默认属性对于布局算法的正常工作来说很重要但我们也希望在使用这个组件的时候可以覆盖这些默认的样式。DatePickerIOS实现这个功能的办法是通过封装一个拥有弹性样式的额外视图然后在内层的视图上应用一个固定样式通过原生传递来的常数生成 // DatePickerIOS.ios.jsvar RCTDatePickerIOSConsts require(react-native).UIManager.RCTDatePicker.Constants;
...render: function() {return (View style{this.props.style}RCTDatePickerIOSref{DATEPICKER}style{styles.rkDatePickerIOS}...//View);}
});var styles StyleSheet.create({rkDatePickerIOS: {height: RCTDatePickerIOSConsts.ComponentHeight,width: RCTDatePickerIOSConsts.ComponentWidth,},
});常量RCTDatePickerIOSConsts在原生代码中导出从一个组件的实际布局上获取到 // RCTDatePickerManager.m- (NSDictionary *)constantsToExport
{UIDatePicker *dp [[UIDatePicker alloc] init];[dp layoutIfNeeded];return {ComponentHeight: (CGRectGetHeight(dp.frame)),ComponentWidth: (CGRectGetWidth(dp.frame)),DatePickerModes: {time: (UIDatePickerModeTime),date: (UIDatePickerModeDate),datetime: (UIDatePickerModeDateAndTime),}};
}本向导覆盖了包装原生组件所需了解的许多方面不过你可能还有很多知识需要了解譬如特殊的方式来插入和布局子视图。如果你想更深入了解可以阅读RCTMapManager和其它的组件的源代码。 本文转自React Native中文网http://reactnative.cn/docs/0.20/native-component-ios.html#content