react native 学习之 native modules

翻译自https://facebook.github.io/react-native/docs/native-modules-ios.html

 

Native Modules

很多情况下,app需要使用原生的api,或者是用一些已经用OC、Swift或C++写好的模块,又或者需要写出更高效率的、或多线程的代码来支撑图像处理、数据库或其它高要求的需求。

React Native的设计当然是支持我们使用原生特性的,以使平台本身的能力得以完全发挥。不过这相对来说是比较进阶的功能,他们的存在虽然是必要的,但在日常开发中并不是必须要用到的。如果RN不支持摸个你想要用到的原生特性,你可以自己做支持。

这是一个高阶教程,介绍了如何搭建一个原生模块。阅读者需要对OC或Swift以及一些核心原生模块(eg.Foundation, UIKit)有一定的了解。

 

iOS Calendar Module Example

本教程以iOS Calendar Api为例。我们要通过JavaScript来使用iOS calendar。

一个原生模块,就是一个实现了RCTBridgeModule协议的Objective-C类。RCT是ReaCT的缩写。

// 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格式的对象类型,如:

  • string (NSString)
  • number (NSIntegerfloatdoubleCGFloatNSNumber)
  • boolean (BOOLNSNumber)
  • array (NSArray) of any types from this list
  • object (NSDictionary) with string keys and values of any type from this list
  • function (RCTResponseSenderBlock)

同时也支持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对象。

 

posted @ 2017-05-21 19:36  遇到不会的多学习!  阅读(6394)  评论(0编辑  收藏  举报