在线文档教程

Native UI Components

Native UI Components

需要本机代码的项目

此页面仅适用于react-native init使用Create React Native App 制作的或使用此类应用程序弹出的项目。有关弹出的更多信息,请参阅创建React Native App存储库的指南。

有许多原生UI小部件可以在最新的应用程序中使用 - 其中一些是该平台的一部分,另一些则作为第三方库提供,并且还可能在您自己的产品组合中使用。反应本土有几个最关键的平台组件已经包裹着,像ScrollViewTextInput,但不是所有的人,肯定不是你可能会自己写的前一个应用程序的。幸运的是,将这些现有组件与您的React Native应用程序无缝集成非常简单。

就像本地模块指南一样,这也是一个更高级的指南,假设你对iOS编程有些熟悉。本指南将向您展示如何构建本地UI组件,引导您完成MapView核心React Native库中现有组件的子集的实现。

iOS MapView示例

比方说,我们希望为我们的应用添加一个交互式地图 - 可以使用MKMapView,我们只需要使其可用于JavaScript。

原生视图由子类创建和操作RCTViewManager。这些子类在功能上与视图控制器类似,但基本上是单例 - 每个桥只创建一个实例。它们向本地视图公开本地视图,根据需要将视图RCTUIManager委托给它们以设置和更新视图的属性。该RCTViewManagers为通常还对意见的代表,通过桥事件发送回JavaScript的。

公开视图很简单:

  • RCTViewManager创建组件管理器的子类。

  • 添加RCT_EXPORT_MODULE()标记宏。

  • 实施该-(UIView *)view方法。

// RNTMapManager.m #import <MapKit/MapKit.h> #import <React/RCTViewManager.h> @interface RNTMapManager : RCTViewManager @end @implementation RNTMapManager RCT_EXPORT_MODULE() - (UIView *)view { return [[MKMapView alloc] init]; } @end

注意:不要尝试在通过方法公开的实例上设置framebackgroundColor属性。React Native会覆盖您的自定义类设置的值,以匹配您的JavaScript组件的布局道具。如果你需要这个粒度的控制,可能会更好地包装你想在另一个样式中的实例,而不是返回包装。有关更多上下文,请参阅第2948期。UIView-viewUIViewUIViewUIView

在上面的例子中,我们在前面加上了我们的类名RNT。前缀用于避免与其他框架的名称冲突。Apple框架使用双字母前缀,React Native RCT用作前缀。为了避免名称冲突,我们建议使用除RCT您自己的类以外的三个字母的前缀。

那么你只需要一点JavaScript就可以使它成为一个可用的React组件:

// MapView.js import { requireNativeComponent } from 'react-native'; // requireNativeComponent automatically resolves 'RNTMap' to 'RNTMapManager' module.exports = requireNativeComponent('RNTMap', null // MyApp.js import MapView from './MapView.js'; ... render() { return <MapView style={{ flex: 1 }} />; }

请务必在RNTMap这里使用。我们希望在这里要求经理,这将公开我们的经理在Javascript中使用的视图。

注意:渲染时,不要忘记拉伸视图,否则你会盯着空白屏幕。

render() { return <MapView style={{flex: 1}} />; }

现在,这是一个全功能的JavaScript本地地图视图组件,通过捏缩放和其他本地手势支持完成。我们无法真正从JavaScript控制它,尽管:(

属性

我们可以做的第一件事是让这个组件更加可用,这是为了弥合一些本地属性。假设我们希望能够禁用缩放并指定可见区域。禁用缩放是一个简单的布尔值,所以我们添加这一行:

// RNTMapManager.m RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)

请注意,我们明确指定类型为BOOL- React Native RCTConvert在引擎盖下通过桥接器转换各种不同的数据类型,而错误的值将显示方便的“RedBox”错误,以便您尽快知道存在问题。当事情如此简单时,整个实现将由这个宏来为您处理。

现在为了实际禁用缩放,我们在JS中设置属性:

// MyApp.js <MapView zoomEnabled={false} style={{ flex: 1 }} />;

要记录我们的MapView组件的属性(以及它们接受哪些值),我们将添加一个包装组件并用React记录界面PropTypes

// MapView.js import PropTypes from 'prop-types'; import React from 'react'; import { requireNativeComponent } from 'react-native'; class MapView extends React.Component { render() { return <RNTMap {...this.props} />; } } MapView.propTypes = { /** * A Boolean value that determines whether the user may use pinch * gestures to zoom in and out of the map. */ zoomEnabled: PropTypes.bool, }; var RNTMap = requireNativeComponent('RNTMap', MapView module.exports = MapView;

现在我们有一个很好的记录包装组件,很容易处理。请注意,我们改变了第二个参数requireNativeComponentnull到新的MapView包装组件。这允许基础结构验证propTypes与本地道具相匹配,以减少ObjC和JS代码之间不匹配的可能性。

接下来,让我们添加更复杂的region道具。我们从添加本机代码开始:

// RNTMapManager.m RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView) { [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES]; }

好的,这比BOOL我们以前的简单情况更复杂。现在我们有一个MKCoordinateRegion需要转换函数的类型,并且我们有自定义代码,这样当我们从JS设置区域时,视图将会动画。在我们提供的函数体内,json指的是从JS传递的原始值。还有一个view变量可以让我们访问管理器的视图实例,defaultView如果JS向我们发送一个空的标记,我们使用该变量将属性重置为默认值。

你可以为你的视图编写任何你想要的转换函数 - 这里是MKCoordinateRegion通过一个类别的实现RCTConvert。它使用一个已经存在的ReactNative类别RCTConvert+CoreLocation

// RNTMapManager.m #import "RCTConvert+Mapkit.m" // RCTConvert+Mapkit.h #import <MapKit/MapKit.h> #import <React/RCTConvert.h> #import <CoreLocation/CoreLocation.h> #import <React/RCTConvert+CoreLocation.h> @interface RCTConvert (Mapkit) + (MKCoordinateSpan)MKCoordinateSpan:(id)json; + (MKCoordinateRegion)MKCoordinateRegion:(id)json; @end @implementation 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] }; } @end

这些转换函数的设计目的是通过显示“RedBox”错误,并在遇到丢失键或其他开发人员错误时返回标准初始化值,从而安全地处理JS可能抛出的任何JSON。

为了完成对regionprop的支持,我们需要将它记录在文件中propTypes(否则我们会得到一个本地prop没有记录的错误),那么我们可以像设置任何其他prop一样设置它:

// MapView.js MapView.propTypes = { /** * A Boolean value that determines whether the user may use pinch * gestures to zoom in and out of the map. */ zoomEnabled: PropTypes.bool, /** * The region to be displayed by the map. * * The region is defined by the center coordinates and the span of * coordinates to display. */ region: PropTypes.shape{ /** * Coordinates for the center of the map. */ latitude: PropTypes.number.isRequired, longitude: PropTypes.number.isRequired, /** * Distance between the minimum and the maximum latitude/longitude * to be displayed. */ latitudeDelta: PropTypes.number.isRequired, longitudeDelta: PropTypes.number.isRequired, }), }; // MyApp.js render() { var region = { latitude: 37.48, longitude: -122.16, latitudeDelta: 0.1, longitudeDelta: 0.1, }; return ( <MapView region={region} zoomEnabled={false} style={{ flex: 1 }} /> }

在这里你可以看到,该区域的形状在JS文档中是明确的 - 理想情况下,我们可以对这些东西进行编码,但这还没有发生。

有时,您的本地组件会拥有一些您不希望它们成为相关React组件的API的一部分的特殊属性。例如,为原始本机事件创建Switch了一个自定义onChange处理程序,并公开一个onValueChange仅使用布尔值而非原始事件调用的处理程序属性。既然你不希望这些本地唯一的属性成为API的一部分,你不想把它们放入propTypes,但如果你不这样做,你会得到一个错误。解决方案只是将它们添加到nativeOnly选项中,例如

var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, { nativeOnly: { onChange: true } }

活动

所以现在我们有一个本地地图组件,我们可以从JS轻松控制,但我们如何处理来自用户的事件,如捏缩放或平移以更改可见区域?

到目前为止,我们刚刚MKMapView从经理的-(UIView *)view方法中返回了一个实例。我们不能添加新的属性,MKMapView所以我们必须创建一个新的子类MKMapView,我们用它来查看。然后我们可以onRegionChange在这个子类上添加一个回调函数:

// RNTMapView.h #import <MapKit/MapKit.h> #import <React/RCTComponent.h> @interface RNTMapView: MKMapView @property (nonatomic, copy) RCTBubblingEventBlock onRegionChange; @end // RNTMapView.m #import "RNTMapView.h" @implementation RNTMapView @end

接下来,声明一个事件处理程序属性RNTMapManager,使其成为它公开的所有视图的委托,并通过从本地视图调用事件处理程序块将事件转发给JS。

// RNTMapManager.m #import <MapKit/MapKit.h> #import <React/RCTViewManager.h> #import "RNTMapView.h" #import "RCTConvert+Mapkit.m" @interface RNTMapManager : RCTViewManager <MKMapViewDelegate> @end @implementation RNTMapManager RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTBubblingEventBlock) RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView) { [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES]; } - (UIView *)view { RNTMapView *map = [RNTMapView new]; map.delegate = self; return map; } #pragma mark MKMapViewDelegate - (void)mapView:(RNTMapView *)mapView regionDidChangeAnimated:(BOOL)animated { if (!mapView.onRegionChange) { return; } MKCoordinateRegion region = mapView.region; mapView.onRegionChange(@{ @"region": @{ @"latitude": @(region.center.latitude), @"longitude": @(region.center.longitude), @"latitudeDelta": @(region.span.latitudeDelta), @"longitudeDelta": @(region.span.longitudeDelta), } } } @end

在委托方法中-mapView:regionDidChangeAnimated:,使用区域数据在相应的视图上调用事件处理程序块。调用onRegionChange事件处理程序块会导致在JavaScript中调用相同的回调支持。这个回调函数与原始事件一起被调用,我们通常在包装器组件中处理这个事件来创建一个更简单的API:

// MapView.js class MapView extends React.Component { _onRegionChange = (event) => { if (!this.props.onRegionChange) { return; } // process raw event... this.props.onRegionChange(event.nativeEvent } render() { return ( <RNTMap {...this.props} onRegionChange={this._onRegionChange} /> } } MapView.propTypes = { /** * Callback that is called continuously when the user is dragging the map. */ onRegionChange: PropTypes.func, ... }; // MyApp.js class MyApp extends React.Component { onRegionChange(event) { // Do stuff with event.region.latitude, etc. } render() { var region = { latitude: 37.48, longitude: -122.16, latitudeDelta: 0.1, longitudeDelta: 0.1, }; return ( <MapView region={region} zoomEnabled={false} onRegionChange={this.onRegionChange} /> } }

样式

由于我们所有的原生反应视图都是子类UIView,因此大多数样式属性都可以像您期望的那样工作。有些组件需要一个默认的样式,但是,例如UIDatePicker,这是一个固定的大小。此默认样式对于布局算法按预期工作很重要,但我们也希望能够在使用组件时覆盖默认样式。DatePickerIOS通过将本地组件包装在具有灵活样式的额外视图中,并在内部本地组件上使用固定样式(使用本机传入的常量生成)执行此操作:

// DatePickerIOS.ios.js import { UIManager } from 'react-native'; var RCTDatePickerIOSConsts = UIManager.RCTDatePicker.Constants; ... render: function() { return ( <View style={this.props.style}> <RCTDatePickerIOS ref={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), } }; }

本指南涵盖了桥接自定义本机组件的许多方面,但您可能需要考虑更多内容,例如用于插入和布置子视图的自定义挂钩。如果你想更深入,请查看一些实现组件的源代码