React Native 的宗旨是『Learn once, write anywhere.』,换句话说,在 RN 开发中,大多数时候,开发者并不需要关心 native 那一层,安心编写react组件以及相应的业务逻辑就可以了。
不过有的时候,也会需要用到原生的模块,比如:
这种情况下,可以利用 RN 导出原生模块给 js 调用,下文会简单举例说明。文中例子可以在 笔者的Github 上找到,也建议查看官方文档获取更详细说明。
首先,创建头文件 TodoList.h,TodoList 实现了 RCTBridgeModule 协议。
// TodoList.h
#import <React/RCTBridgeModule.h>
@interface TodoList : NSObject <RCTBridgeModule>
@end
接着,创建 TodoList.m。代码简单解释下:
RCT_EXPORT_MODULE();
:将 TodoList
模块导出;RCT_EXPORT_METHOD(add)
:导出 add
方法,:
后是参数列表(可多个);// TodoList.m
#import "TodoList.h"
#import <React/RCTLog.h>
@implementation TodoList
NSMutableArray *list = nil;
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(create)
{
list = [[NSMutableArray alloc] init];
}
RCT_EXPORT_METHOD(add:(NSString *)item)
{
NSLog(@"add: %@", item);
[list addObject:@{ @"desc":item, @"done": @0 }];
}
从 js 中调用自定义的原生模块非常简单,代码如下:
import {NativeModules} from 'react-native';
const TodoList = NativeModules.TodoList;
TodoList.create();
TodoList.add('起床');
xcode中打印日志:
2019-11-08 21:24:32.636547+0800 RNTest[5027:50749999] add: 起床
在前端开发中,函数回调非常常见,RN 中导出的原生方法,也支持传入回调方法,如下所示。
// 例子:方法导出,支持回调
RCT_EXPORT_METHOD(addWithCallback:(NSString *)item callback:(RCTResponseSenderBlock)callback)
{
NSLog(@"addWithCallback: %@", item);
[list addObject:@{ @"desc":item, @"done": @0 }];
callback(@[[NSNull null], list]);
}
在 index.js 中新增调用:
// 例子:函数回调
TodoList.addWithCallback('吃早餐', (error, list) => {
if (error === null) {
console.log(`[addWithCallback] list.length == ${list.length}`);
}
});
输出如下:
[addWithCallback] list.length == 2
调用原生模块,除了函数回调,Native Module 还可以主动抛出事件,在 js 层进行监听处理,例子如下。
首先,对 TodoList.h 进行修改,继承 RCTEventEmitter
。
// TodoList.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface TodoList : RCTEventEmitter <RCTBridgeModule>
@end
接着,导出 addAndTriggerEvent
方法,里面主动抛出 ItemAdded 事件。注意,通过 sendEventWithName 抛出的事件需要在 supportedEvents 里进行注册,不然运行期会报错。
// TodoList.m
// 返回的数组为支持的事件名列表
- (NSArray<NSString *> *)supportedEvents
{
return @[@"ItemAdded"];
}
// 例子:事件抛出
RCT_EXPORT_METHOD(addAndTriggerEvent:(NSString *)item)
{
NSLog(@"addAndTriggerEvent: %@", item);
[list addObject:@{ @"desc":item, @"done": @0 }];
[self sendEventWithName:@"ItemAdded" body:list];
}
最后,在 index.js 监听 ItemAdded 事件,搞定。
const TodoList = NativeModules.TodoList;
const todoListEmitter = new NativeEventEmitter(TodoList);
TodoList.create();
// 监听事件
todoListEmitter.addListener('ItemAdded', list => {
console.log(`[ItemAdded] list.length == ${list.length}`);
});
// 添加item,在这个方法里会抛出事件
TodoList.addAndTriggerEvent('上班');
输出如下:
[ItemAdded] list.length == 3
对于异步操作来说,返回 Promise 实例通常是个不错的选择,RN原生函数 也进行了支持了这个特性。
首先,导出方法 addAndReturnPromise,这个方法接收两个参数 item、isResolved,根据 isResolved 的值决定 Promise 实例最终的状态。
// 例子:返回Promise实例
RCT_REMAP_METHOD(addAndReturnPromise,
item:(NSString *) item
ifResolved:(int) ifResolved
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSLog(@"addAndTriggerEvent: %@", item);
[list addObject:@{ @"desc":item, @"done": @0 }];
if (ifResolved) {
resolve([NSString stringWithFormat:@"%@ is accepted.", item]);
} else {
NSString *domain = @"com.chyingp.www";
NSDictionary *userInfo = @{ @"desc": @"nonsense" };
NSInteger code = -1;
NSError *error =[NSError errorWithDomain:domain code:code userInfo:userInfo];
NSString *errMsg = [NSString stringWithFormat:@"%@ is not accepted.", item];
reject(@"ErrorFromNativeModule", errMsg, error);
}
}
其次,在 index.js 中添加调用
// 例子:返回Promise实例
const resolver = msg => console.log(`[addAndReturnPromise] [resolved] ${msg}`);
const rejecter = error => console.log(`[addAndReturnPromise] [rejected] ${error.message}`);
TodoList.addAndReturnPromise('加班', true).then(resolver).catch(rejecter);
TodoList.addAndReturnPromise('休息', false).then(resolver).catch(rejecter);
输出如下(有点悲伤):
[addAndReturnPromise] [resolved] 加班 is accepted.
[addAndReturnPromise] [rejected] 休息 is not accepted.
2019.11.07-calling-native-module-from-js-in-rn/
https://facebook.github.io/react-native/docs/native-modules-ios