React Native使用指南-原生UI组件

在如今的App中,已经有成千上万的原生UI部件了——其中的一些是平台的一部分,另一些可能来自于一些第三方库,而且可能你自己还收藏了很多。React Native已经封装了大部分最常见的组件,譬如ScrollViewTextInput,但不可能封装全部组件。而且,说不定你曾经为自己以前的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.h"@interface RCTMapManager : RCTViewManager
@end@implementation 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"]]};
}@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]};
}

这些转换函数被设计为可以安全的处理任何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 "UIView+React.h"@interface RCTMapManager() <MKMapViewDelegate>
@end@implementation 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500026.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[网络]------长连接和短连接

本文重点介绍&#xff1a; 长连接和短连接的定义&#xff0c;优缺点以及使用场景 前提须知: 1.HTTP/1.0默认使用短连接,HTTP/1.1开始,默认使用长连接 2.HTTP协议的长连接和短连接,实质是就是TCP协议的长连接和短连接 3.tcp协议建立连接需要三次握手,这个过程会耗费网络资源…

React Native使用指南-使用链接库

并不是所有的APP都需要使用全部的原生功能&#xff0c;包含支持全部特性的代码会增大应用的体积。但我们仍然希望能让你简单地根据自己的需求添加需要的特性。 在这种思想下&#xff0c;我们把许多特性都发布成为互不相关的静态库。 大部分的库只需要拖进两个文件就可以使用了&…

[网络]------TCP UDP HTTP Socket 区别

关于这几个的概念,网上已经很全面了,在这个做个笔记: 前提须知: 1.协议就是大家提前约定的一种规范,后人照着这个遵循就可以,也可以将语言理解为一种协议 2.网络通信的世界中,有七层协议(应用层,表示层,会话层,传输层,网络层,数据链路层,物理层) 正题: 1.TCP和UDP都属于传…

React Native使用指南-在设备上运行

注意在iOS设备上运行React Native应用需要一个Apple Developer account并且把你的设备注册为测试设备。本向导只包含React Native相关的主题。 译注&#xff1a;从XCode 7起&#xff0c;在自己的设备上调试App不再需要开发者账户了。 从设备访问开发服务器 在启用开发服务器的情…

java 贪吃蛇游戏

前言 此实现较为简陋&#xff0c;如有错误请指正。 其次代码中的图片需要自行添加地址并修改。 主类 public class Main { public static void main(String[] args) { new myGame(); } } 1 2 3 4 5 游戏类 import javax.swing.*; import java.awt.eve…

java方向好看的书

从Lucene到ElasticSearch:全文检索实战 大数据架构详解&#xff1a;从数据获取到深度学习 代码整洁之道 架构解密&#xff1a;从分布式到微服务 从Paxos到Zookeeper分布式一致性原理与实践 大型网站技术架构 核心原理与案例分析 分布式服务架构&#xff1a;原理、设计与实…

React Native使用指南-植入原生应用

由于React并没有假设你其余部分的技术栈——它通常只作为MVC模型中的V存在——它也很容易嵌入到一个并非由React Native开发的应用当中。实际上&#xff0c;它可以和常见的许多工具结合&#xff0c;譬如CocoaPods。 需求 CocoaPods – gem install cocoapodsNode.js 安装 nvm&a…

[数据库]---nosql,非关系型数据库整理

1.关系型数据库与非关系型数据库的区别 Tables关系型数据库非关系型数据库成本好的收费开源的,都免费的存储与查询硬盘存储,相对查的慢内存存储,查的快存储格式只能是基本格式多种多样年龄出现时间较长,较成熟后起之秀,不可小觑扩展性join等不好扩展方便集群事务强事务弱事务查…

React Native开发指南-在原生和React Native间通信

通过植入原生应用和原生UI组件两篇文档&#xff0c;我们学习了React Native和原生组件的互相整合。在整合的过程中&#xff0c;我们会需要在两个世界间互相通信。有些方法已经在其他的指南中提到了&#xff0c;这篇文章总结了所有可行的技术。 简介 React Native是从React中得到…

数据迁移记录

做数据迁移,数据库是分两片,每片一主两从,没有读写分离 前提: 使用服务器1:2核4G内存40G硬盘 线程池:5个 每个线程分页查询,每页1000条 平均数据是一天一个线程,一天3万数据,所以大概就是30页的分页深度 服务器1的数据: cpu使用率在74%上下(有点高,2核的毕竟比不过4核的…

RPC协议简介

一、概述 1.英文原义&#xff1a;Remote Procedure Call Protocol 2.中文释义&#xff1a;&#xff08;RFC-1831&#xff09;远程调用协议 。 3.注解&#xff1a;一种通过网络从远程计算机程序上请求服务&#xff0c;而不需要了解底层网络技术的协议。 4.说明&#xff1a;RPC…

mysql分页查询报错,及解决

mysql分页查询报错: 前提: 1.每页1000条数据 2.查到57页的时候,就报错了 以下是错误信息: org.springframework.jdbc.UncategorizedSQLException: ### Error querying database. Cause: java.sql.SQLException: ...省略中间... received message larger than max (1844105…

RPC协议与Web Service

一、引入 我们每天都在使用浏览器来上网冲浪, 在查找自己需要的资源, HTTP协议自然是我们使用的最多的 一种, 我们尽情地享受着这种信息高速路的快感,却没有试图去了解我们是如何获得这些资源的? 它是一种什么样的设计理念? 我们也偶尔会使用 Gtalk来和自己的同事或者朋友来聊…

如何设置电脑开机自动提示(防止忘打卡等)

如何设置电脑开机自动提示: 第一种方法: 第一步:CtrlR 输入mmc命令打开 文件-添加/删除管理单元-组策略对象编辑器-添加-确定-确定(都是默认的)。 第二步:退出小框,会看见有个”本地计算机 策略”,打开-计算机配置-windows设置-安全设置-本地策略-安全选项 双击”交互式登…

RestFull架构

1 什么是REST REST全称是Representational State Transfer&#xff0c;中文意思是表述性状态转移。 它首次出现在2000年Roy Fielding的博士论文中&#xff0c;Roy Fielding是 HTTP 规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的&#xff0c;就是想在符合架构原…

SpringMVC默认访问路径配置

SpringMVC默认访问路径配置 需求:只访问域名,不加任何action路径,想访问默认的一个action. 比如www.jd.com,就跳到京东首页,会加载出辣么多东西来 关于这个的方法,网上有千千万种,在这里只是记录一种常用的, 亲测这个可以用,而且我们的项目一直都是这个套路. 第一步,web.x…

controller中执行main方法报错NoClassDefFoundError: javax/servlet/http/HttpServletResponse

controller中执行main方法报了这个错:NoClassDefFoundError: javax/servlet/http/HttpServletResponse,如下图: NoClassDefFoundError: javax/servlet/http/HttpServletResponse NoClassDefFoundError: javax/servlet/http/HttpServletRequest同理 原因是本地没有引入servlet…

初探Backbone

Backbone简介 中文API&#xff1a;http://www.css88.com/doc/backbone/ 英文API&#xff1a;http://backbonejs.org/ Backbone是构建javascript应用程序的一个优秀的类库。他简洁、轻量级、功能实在。 backbone采用MVC模式&#xff0c;本身提供了模型、控制器和视图从而我们应用…

订单量的监控

要解决的问题 : 1.在电商项目中,如何准确的知道当前订单量是正常的 2.如何在订单量突变后快速感知 解决思路 : 实现一个关于订单量的监控系统,将历史数据与实时数据做对比,因为每天的订单量,基本都是一个相似的变化范围,比如凌晨4点的单量是一天中最少的,节假日的单量要小于工…

JQuery初探

一、JQuery是什么&#xff1f; JQuery 是一套JavaScript库&#xff0c; 使用它&#xff0c;可以很方便的进行 JavaScript的编程。比如&#xff1a; 获取页面元素&#xff0c; 修改页面元素的CSS样式等等都可以以很简单的语法完成。节省代码行数和减少开发的时间。 物理上来看就…