React Native使用指南-原生模块
我们把React Native设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不认为它应当在日常开发的过程中经常出现,但具备这样的能力是很重要的。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。
本文是关于如何封装原生模块的高级向导,我们假设您已经具备Objective-C或者Swift,以及iOS核心库(Foundation、UIKit)的相关知识。
iOS 日历模块演示
本向导将会用iOS日历API作为示例。我们的目标就是在Javascript中可以访问到iOS的日历功能。
在React Native中,一个“原生模块”就是一个实现了“RCTBridgeModule”协议的Objective-C类,其中RCT是ReaCT的缩写。
// CalendarManager.h
#import "RCTBridgeModule.h"@interface CalendarManager : NSObject <RCTBridgeModule>
@end
// CalendarManager.m
@implementation CalendarManagerRCT_EXPORT_MODULE();@end
你必须明确的声明要给Javascript导出的方法,否则React Native不会导出任何方法。声明通过RCT_EXPORT_METHOD()
宏来实现:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
var CalendarManager = require('react-native').NativeModules.CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
注意: Javascript方法名
导出到Javascript的方法名是Objective-C的方法名的第一个部分。React Native还定义了一个
RCT_REMAP_METHOD()
宏,它可以指定Javascript方法名。当许多方法的第一部分相同的时候用它来避免在Javascript端的名字冲突。
桥接到Javascript的方法返回值类型必须是void
。React Native的桥接操作是异步的,所以要返回结果给Javascript,你必须通过回调或者触发事件来进行。(参见本文档后面的部分)
参数类型
RCT_EXPORT_METHOD
支持所有标准JSON类型,包括:
- string (
NSString
) - number (
NSInteger
,float
,double
,CGFloat
,NSNumber
) - boolean (
BOOL
,NSNumber
) - array (
NSArray
) 包含本列表中任意类型 - map (
NSDictionary
) 包含string类型的键和本列表中任意类型的值 - function (
RCTResponseSenderBlock
)
在我们的CalendarManager
例子里,我们需要把事件的时间交给原生方法。我们不能在桥接通道里传递Date对象,所以需要把日期转化成字符串或数字来传递。我们可以这么实现原生函数:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSNumber *)secondsSinceUnixEpoch)
{NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch];
}
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString)
{NSDate *date = [RCTConvert NSDate:ISO8601DateString];
}
不过我们可以依靠自动类型转换的特性,跳过手动的类型转换,而直接这么写:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date)
{// Date is ready to use!
}
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); // 把日期以unix时间戳形式传递
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // 把日期以ISO-8601的字符串形式传递
两个值都会被转换为正确的NSDate
类型。但如果提供一个不合法的值,譬如一个Array
,则会产生一个“红屏”报错信息。
#import "RCTConvert.h"RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details)
{NSString *location = [RCTConvert NSString:details[@"location"]];NSDate *time = [RCTConvert NSDate:details[@"time"]];...
}
CalendarManager.addEvent('Birthday Party', {location: '4 Privet Drive, Surrey',time: date.toTime(),description: '...'
})
注意: 关于数组和映射
Objective-C并没有提供确保这些结构体内部值的类型的方式。你的原生模块可能希望收到一个字符串数组,但如果JavaScript在调用的时候提供了一个混合number和string的数组,你会收到一个
NSArray
,里面既有NSNumber
也有NSString
。对于数组来说,RCTConvert
提供了一些类型化的集合,譬如NSStringArray
或者UIColorArray
,你可以用在你的函数声明中。对于映射而言,开发者有责任自己调用RCTConvert
的辅助方法来检测和转换值的类型。
回调函数
警告
本章节内容目前还处在实验阶段,因为我们还并没有太多的实践经验来处理回调函数。
原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给JavaScript。
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{NSArray *events = ...callback(@[[NSNull null], events]);
}
RCTResponseSenderBlock
只接受一个参数——传递给JavaScript回调函数的参数数组。在上面这个例子里我们用Node.js的常用习惯:第一个参数是一个错误对象(没有发生错误的时候为null),而剩下的部分是函数的返回值。
CalendarManager.findEvents((error, events) => {if (error) {console.error(error);} else {this.setState({events: events});}
})
原生模块通常只应调用回调函数一次。但是,它可以保存callback并在将来调用。这在封装那些通过“委托函数”来获得返回值的iOS API时最为常见。RCTAlertManager
中就属于这种情况。
如果你想传递一个更接近Error
类型的对象给Javascript,可以用RCTUtils.h
提供的RCTMakeError
函数。现在它仅仅是发送了一个和Error结构一样的dictionary给Javascript,但我们考虑在将来版本里让它产生一个真正的Error
对象。
Promises
译注:这一部分涉及到较新的js语法和特性,不熟悉的读者建议先阅读ES6的相关书籍和文档。
RCT_REMAP_METHOD(findEvents,resolver:(RCTPromiseResolveBlock)resolverejecter:(RCTPromiseRejectBlock)reject))
{NSArray *events = ...if (events) {resolve(events);} else {reject(error);}
}
async function updateEvents() {try {var events = await CalendarManager.findEvents();this.setState({ events });} catch (e) {console.error(e);}
}updateEvents();
多线程
- (dispatch_queue_t)methodQueue
{return dispatch_get_main_queue();
}
- (dispatch_queue_t)methodQueue
{return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 在这里执行长时间的操作...// 你可以在任何线程/队列中执行回调函数callback(@[...]);});
}
注意: 在模块之间共享分发队列
methodQueue
方法会在模块被初始化的时候被执行一次,然后会被React Native的桥接机制保存下来,所以你不需要自己保存队列的引用,除非你希望在模块的其它地方使用它。但是,如果你希望在若干个模块中共享同一个队列,则需要自己保存并返回相同的队列实例;仅仅是返回相同名字的队列是不行的。
导出常量
原生模块可以导出一些常量,这些常量在JavaScript端随时都可以访问。用这种方法来传递一些静态数据,可以避免通过bridge进行一次来回交互。
- (NSDictionary *)constantsToExport
{return @{ @"firstDayOfTheWeek": @"Monday" };
}
console.log(CalendarManager.firstDayOfTheWeek);
但是注意这个常量仅仅在初始化的时候导出了一次,所以即使你在运行期间改变constantToExport
返回的值,也不会影响到JavaScript环境下所得到的结果。
枚举常量
用NS_ENUM
定义的枚举类型必须要先扩展对应的RCTConvert方法才可以作为函数参数传递。
typedef NS_ENUM(NSInteger, UIStatusBarAnimation) {UIStatusBarAnimationNone,UIStatusBarAnimationFade,UIStatusBarAnimationSlide,
};
@implementation RCTConvert (StatusBarAnimation)RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)},UIStatusBarAnimationNone, integerValue)
@end
- (NSDictionary *)constantsToExport
{return @{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide) }
};RCT_EXPORT_METHOD(updateStatusBarAnimation:(UIStatusBarAnimation)animationcompletion:(RCTResponseSenderBlock)callback)
你的枚举现在会用上面提供的选择器进行转换(上面的例子中是integerValue
),然后再传递给你导出的函数。
给Javascript发送事件
即使没有被JavaScript调用,本地模块也可以给JavaScript发送事件通知。最直接的方式是使用eventDispatcher
:
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"@implementation CalendarManager@synthesize bridge = _bridge;- (void)calendarEventReminderReceived:(NSNotification *)notification
{NSString *eventName = notification.userInfo[@"name"];[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"body:@{@"name": eventName}];
}@end
var { NativeAppEventEmitter } = require('react-native');var subscription = NativeAppEventEmitter.addListener('EventReminder',(reminder) => console.log(reminder.name)
);
...
// 千万不要忘记忘记取消订阅, 通常在componentWillUnmount函数中实现。
subscription.remove();
更多的给JavaScript发送事件的例子,参见RCTLocationObserver
.
从Swift导出
Swift不支持宏,所以从Swift向React Native导出类和函数需要多做一些设置,但是大致与Objective-C是相同的。
假设我们已经有了一个一样的CalendarManager
,不过是用Swift实现的类:
// CalendarManager.swift@objc(CalendarManager)
class CalendarManager: NSObject {@objc func addEvent(name: String, location: String, date: NSNumber) -> Void {// Date is ready to use!}}
注意: 你必须使用@objc标记来确保类和函数对Objective-C公开。
接着,创建一个私有的实现文件,并将必要的信息注册到React Native中。
// CalendarManagerBridge.m
#import "RCTBridgeModule.h"@interface RCT_EXTERN_MODULE(CalendarManager, NSObject)RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)@end
请注意,一旦你在IOS中混用2种语言, 你还需要一个额外的桥接头文件,称作“bridging header”,用来导出Objective-C文件给Swift。如果你是通过Xcode菜单中的File>New File
来创建的Swift文件,Xcode会自动为你创建这个头文件。在这个头文件中,你需要引入RCTBridgeModule.h
。
// CalendarManager-Bridging-Header.h
#import "RCTBridgeModule.h"
同样的,你也可以使用RCT_EXTERN_REMAP_MODULE
和RCT_EXTERN_REMAP_METHOD
来改变导出模块和方法的JavaScript调用名称。 了解更多信息,请参阅RCTBridgeModule
.
本文转自React Native中文网:http://reactnative.cn/docs/0.20/native-modules-ios.html#content
React Native使用指南-原生模块相关推荐
- React Native使用指南-原生UI组件
在如今的App中,已经有成千上万的原生UI部件了--其中的一些是平台的一部分,另一些可能来自于一些第三方库,而且可能你自己还收藏了很多.React Native已经封装了大部分最常见的组件,譬如Scr ...
- React Native开发指南-在原生和React Native间通信
通过植入原生应用和原生UI组件两篇文档,我们学习了React Native和原生组件的互相整合.在整合的过程中,我们会需要在两个世界间互相通信.有些方法已经在其他的指南中提到了,这篇文章总结了所有可行 ...
- 【React Native】在原生和React Native间通信(RN调用原生)
一.从React Native中调用原生方法(原生模块) 原生模块是JS中也可以使用的Objective-C类.一般来说这样的每一个模块的实例都是在每一次通过JS bridge通信时创建的.他们可以导 ...
- React Native使用指南-植入原生应用
由于React并没有假设你其余部分的技术栈--它通常只作为MVC模型中的V存在--它也很容易嵌入到一个并非由React Native开发的应用当中.实际上,它可以和常见的许多工具结合,譬如CocoaP ...
- rn+与android+交互,React native 与Android原生交互方式(一)
前言## 最近在做React Native开发的时候避免不了的需要原生模块和JS之间进行交互,其实RN和原生的通信大致分为两种情况:一种是Android主动向RN端发送事件和数据,另外一种是RN端被动 ...
- Android方法调用实体类的值,React Native调用Android原生方法和传值
8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 创建react native 项目:react-native init callAndroidProject cd ca ...
- 【React Native】iOS原生导航跳转RN页面
上一篇介绍了React Native使用react-navigation进行导航跳转页面,现在我们介绍下原生iOS中怎么导航进一个新的React Native页面. 一.原生跳转React Nativ ...
- React Native使用指南-使用链接库
并不是所有的APP都需要使用全部的原生功能,包含支持全部特性的代码会增大应用的体积.但我们仍然希望能让你简单地根据自己的需求添加需要的特性. 在这种思想下,我们把许多特性都发布成为互不相关的静态库. ...
- React Native调用Android原生代码实现车牌识别功能【附效果图附源码】
转载请注明出处,原文地址:http://blog.csdn.net/lucherr/article/details/71908180 这段时间研究了下React Native,Facebook推出的, ...
最新文章
- python脚本备份网络交换的命令
- Python连接MySQL数据库执行sql语句时的参数问题
- OneNote代码高亮插件(NoteHighLight)
- 请简单解释一下ARP协议和ARP攻击
- ashx PHP文件 优劣,.NET_后缀为 ashx 与 axd 的文件区别浅析,唯一不同的地方是:axd扩展名 - phpStudy...
- 闪屏页面(Splash)开发
- Mac下配置sublime实现LaTeX
- java xms xmx 默认值_JVM启动参数-Xmx的默认值是多少?
- 软件测试—软件测试基础知识—测试用例设计的方法判定表和因果图
- html5首页图标怎么除掉,移动端H5页面端如何除去input输入框的默认样式
- 100阶乘c语言如何实现,求10000的阶乘(c语言代码实现)
- tp5 获取当前的url方法
- c++中显示“无法打开xxx.exe进行写入”
- O2O常见的结算模式是什么 O2O线上线下营销策略有哪些?
- python弧度制转换 三角函数 反三角函数 双曲 反双曲 sin cos tan asin acos atan asinh acosh atanh atanh2
- rsyncd.conf 文件man手册翻译
- mysql中高阶玩法系列(八)
- SpringMVC cookie设置Secure
- flume采集hive日志写到hdfs问题
- dede网站模板制作流程