翻译自https://facebook.github.io/react-native/docs/native-modules-ios.htmlhtml
Native Modulesreact
不少状况下,app须要使用原生的api,或者是用一些已经用OC、Swift或C++写好的模块,又或者须要写出更高效率的、或多线程的代码来支撑图像处理、数据库或其它高要求的需求。ios
React Native的设计固然是支持咱们使用原生特性的,以使平台自己的能力得以彻底发挥。不过这相对来讲是比较进阶的功能,他们的存在虽然是必要的,但在平常开发中并非必需要用到的。若是RN不支持摸个你想要用到的原生特性,你能够本身作支持。git
这是一个高阶教程,介绍了如何搭建一个原生模块。阅读者须要对OC或Swift以及一些核心原生模块(eg.Foundation, UIKit)有必定的了解。github
iOS Calendar Module Example数据库
本教程以iOS Calendar Api为例。咱们要经过JavaScript来使用iOS calendar。react-native
一个原生模块,就是一个实现了RCTBridgeModule协议的Objective-C类。RCT是ReaCT的缩写。api
// CalendarManager.h #import <React/RCTBridgeModule.h> @interface CalendarManager : NSObject <RCTBridgeModule> @end
除了要实现RCTBridgeModule协议以外,还须要执行RCT_EXPORT_MODULE()宏指令,它接受的第一个参数,表示这个模块在JavaScript中使用时的引用名。若是没有传这个参数,那默认OC的类名就是js中的引用名。数组
// CalendarManager.m @implementation CalendarManager // To export a module named CalendarManager RCT_EXPORT_MODULE(); // This would name the module AwesomeCalendarManager instead // RCT_EXPORT_MODULE(AwesomeCalendarManager); @end
若是须要在js中使用CalendarManager中的方法,须要使用RCT_EXPORT_METHOD()宏指令,将方法暴露出去。数据结构
#import "CalendarManager.h" #import <React/RCTLog.h> @implementation CalendarManager RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); }
暴露出去以后,在js文件中调用该方法的方式以下:
import { NativeModules } from 'react-native'; var CalendarManager = NativeModules.CalendarManager; CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
注:JavaScript中的方法名称
暴露到JavaScript中的原生方法的方法名就是原生方法名第一个冒号前的内容。另外,RN定义了一个宏指令RCT_REMAP_METHOD(),能够用来制定方法在JavaScript中的方法名。当原生代码中暴露出去的不一样的方法的方法名,第一个逗号前有相同的内容时,在JavaScript中方法名就会冲突,这个宏指令就用得上了。
暴露的原生方法的返回值类型只能是void,由于React Native的bridge是异步的,因此向JavaScript传递原生方法的调用结果的方式只能经过回调函数或注册事件的方式。
Argument Types
RCT_EXPORT_METHOD支持全部标准的JSON格式的对象类型,如:
NSString
)NSInteger
, float
, double
, CGFloat
, NSNumber
)BOOL
, NSNumber
)NSArray
) of any types from this listNSDictionary
) with string keys and values of any type from this listRCTResponseSenderBlock
)同时也支持RCTConvert class支持的类型。RCTConvert的helper functions接收JSON值,将其转化为Objective-C的类型或类。
在咱们CalendarManager的例子中,咱们想要将日期传给远胜方法,可是咱们不能直接传js的Date类型的对象,咱们须要将data类型的对象转换成字符串或数字。原生方法能够这样写:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull 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! }
在js中调用的方式以下:
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); // passing date as number of seconds since Unix epoch // or CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // passing date as ISO-8601 string
这两种调用都会使原生发放获得正确的NSDate类型的对象。若是是一个不合法的类型,你会看到红盒子的错误信息。
若是CalendarManager.addEvent方法变得愈来愈复杂,参数的数量会愈来愈多,并且不是每个参数都是必要的。这时候就能够考虑,经过字典的形式传入参数。以下:
#import <React/RCTConvert.h> RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details) { NSString *location = [RCTConvert NSString:details[@"location"]]; NSDate *time = [RCTConvert NSDate:details[@"time"]]; ... }
在JavaScript中调用:
CalendarManager.addEvent('Birthday Party', { location: '4 Privet Drive, Surrey', time: date.getTime(), description: '...' })
注:关于array和map
Objective-C不会限制这两种数据结构里的内容的类型。若是原生模块须要一个字符串的数组,而在js中调用的时候,传入了包含数字和字符串的数组,咱们在原生方法中会得到既有NSNumber又有NSString类型的NSArray。对于数组,RCTConvert提供了一些约束类型的数据结构永远方法的声明中,例如NSStringArray,UIColorArray。对于maps,开发者须要利用RCTConvert的方法分别取去检查里面的值类型。
Callbacks
原生模块能够接收一个特别的参数:一个js回调函数,做为原生函数执行过程当中或执行结束后,向js返回结果并经过js进行进一步处理的方法。
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) { NSArray *events = ... callback(@[[NSNull null], events]); }
RCTResponseSenderBlock只接收一个参数,一个传给js回调函数的参数数组。在咱们的例子中,咱们的第一个参数是错误信息,做为回调函数参数数组的第一个元素。
CalendarManager.findEvents((error, events) => { if (error) { console.error(error); } else { this.setState({events: events}); } })
一个原生模块,应该在方法中当即调用回调函数。也能够将回调函数存下来以后再调用,这经常用在委托中,RCTAlertManager就是一个例子。若是回调函数不被出发,将会形成内存泄漏。若是onSuccess和onFail回调同时存在,只应该调用其中一个。
若是想要传递错误信息对象到js中,用RCTUtils.h提供的RCTMakeError。目前它向js传入了一个error-shaped字典,不过将来会自动生成真正的js的Error对象。