参考文档:https://www.reactnative.cn/docs/native-modules-ios
有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块封装;或者你需要复用 Objective-C、Swift 或 C++代码,而不是用 JavaScript 重新实现一遍;又或者你需要实现某些高性能、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
我们把 React Native 设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不认为它应当在日常开发的过程中经常出现,但具备这样的能力是很重要的。如果 React Native 还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。
本文是关于如何封装原生模块的高级向导,我们假设您已经具备 Objective-C 或者 Swift,以及 iOS 核心库(Foundation、UIKit)的相关知识。
话不多讲开始撸代码
OC代码的实现
创建一个文件继承于 RCTEventEmitter 实现协议 RCTBridgeModule
.h文件
//
// CalendarManager.h
// AppDemo
//
//
#import
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface CalendarManager : RCTEventEmitter
@end
NS_ASSUME_NONNULL_END
.m
//
// CalendarManager.m
// AppDemo
//
//
#import "CalendarManager.h"
#import //调用输出的方法
#import
@interface CalendarManager ()
@end
@implementation CalendarManager
/**
*为了实现RCTBridgeModule协议,你的类需要包含RCT_EXPORT_MODULE()宏。这个宏也可以添加一个参数用来指定在 JavaScript 中访问这个模块的名字。如果你不指定,默认就会使用这个 Objective-C 类的名字。如果类名以 RCT 开头,则 JavaScript 端引入的模块名会自动移除这个前缀。
*/
RCT_EXPORT_MODULE()
#pragma mark - 注册方法addEvent
/**
*你必须明确的声明要给 JavaScript 导出的方法,否则 React Native 不会导出任何方法。声明通过RCT_EXPORT_METHOD()宏来实现:
*导出到 JavaScript 的方法名是 Objective-C 的方法名的第一个部分。React Native 还定义了一个RCT_REMAP_METHOD()宏,它可以指定 JavaScript 方法名。因为 JavaScript 端不能有同名不同参的方法存在,所以当原生端存在重载方法时,可以使用这个宏来避免在 JavaScript 端的名字冲突。
*我们需要把事件的时间交给原生方法。我们不能在桥接通道里传递 Date 对象,所以需要把日期转化成字符串或数字来传递
*/
RCT_EXPORT_METHOD(addEvent:(NSString*)name Location:(NSString*)location date:(NSString*)ISO8601DateString){
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
NSDate *date = [RCTConvert NSDate:ISO8601DateString];
NSLog(@"接收到的消息为 name = %@ loction = %@ date = %@",name,location,date);
//调用时间发射器发送消息给React-native
[self calendarEventReminderReceived:[NSNotification notificationWithName:@"sendToRN" object:self userInfo:@{
@"name":@"sendToRN"
}]];
}
#pragma mark - 注册方法addDic
/**
*RCTResponseSenderBlock只接受一个参数——传递给 JavaScript 回调函数的参数数组。在上面这个例子里我们用 Node.js 的常用习惯:第一个参数是一个错误对象(没有发生错误的时候为 null),而剩下的部分是函数的返回值。
*/
RCT_EXPORT_METHOD(addDic:(NSString*)name Details:(NSDictionary*)details callback:(RCTResponseSenderBlock)callBack){
NSString * address = [RCTConvert NSString:details[@"address"]];
NSDate * time = [RCTConvert NSDate:details[@"time"]];
NSString * mark = [RCTConvert NSString:details[@"mark"]];
NSLog(@"获取到的内容是 name = %@ time = %@ address = %@ 备注=%@",name,time,address,mark);
callBack(@[[NSNull null],@[@"我接受到你的消息了",name]]);
}
#pragma mark - 注册方法findEvents 并且带回调
/**
*RCTResponseSenderBlock只接受一个参数——传递给 JavaScript 回调函数的参数数组。在上面这个例子里我们用 Node.js 的常用习惯:第一个参数是一个错误对象(没有发生错误的时候为 null),而剩下的部分是函数的返回值。
*/
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callBack){
callBack(@[[NSNull null],@[@"我接受到你的消息了"]]);
}
//RCT_REMAP_METHOD(findEvents, findEventsWithResolver:(RCTPromiseResolveBlock)resove rejecter:(RCTPromiseRejectBlock)reject){
// NSArray *events = @[@"sss"];
// if (events) {
// resove(events);
// } else {
// NSError *error = [NSError new];
// reject(@"no_events", @"There were no events", error);
// }
//}
#pragma mark - 导出常量
/**
*导出常量
*/
- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };
}
/**
如果你重写了- constantsToExport,那么你还应该实现+ requiresMainQueueSetup,让React Native知道你的模块是否需要在主线程上初始化。否则你会看到一个警告,将来你的模块可能会在后台线程上初始化,除非你显式地用+ requiresMainQueueSetup退出:
*/
+ (BOOL)requiresMainQueueSetup
{
return YES; // only do this if your module initialization relies on calling UIKit!
}
#pragma mark - 向react-natvie 传递消息
- (NSArray *)supportedEvents
{
return @[@"EventReminder"];
}
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
@end
react-native调用
import React, { Component } from 'react'
import { Text, StyleSheet, View, NativeModules,NativeEventEmitter } from 'react-native'
//JavaScript 端的代码可以创建一个包含你的模块的NativeEventEmitter实例来订阅这些事件。
const CalendarManager = NativeModules.CalendarManager
//接收来自原生app的消息
const calendarManagerEmitter = new NativeEventEmitter(CalendarManager);
const subscription = calendarManagerEmitter.addListener(
'EventReminder',
(reminder) => console.log("===>接收到来自原生app的消息",reminder.name)
);
export default class CalendarView extends Component {
state = {
events: []
}
// updateEvents = async ()=>{
// try {
// const events = await CalendarManager.findEvents();
// console.log('===>events',events)
// this.setState({ events });
// } catch (e) {
// console.error(e);
// }
// }
componentDidMount() {
//调用addEvent方法
var date = new Date('2016-12-15 10:20')
CalendarManager.addEvent(
'Birthday Party',
'山东',
date.toISOString()
)
//调用addDic方法
CalendarManager.addDic('生日派对',
{
time: date.getTime(),
address: '山东',
mark: '请务必光临',
},
(error, events) => {
if (error) {
alert(error)
} else {
alert(events)
}
}
)
//调用findEvents方法
CalendarManager.findEvents((error, events) => {
if (error) {
console.log(error);
} else {
console.log('===>', events)
this.setState({ events: events });
}
})
//调用导出常量方法
console.log('===>导出常量',CalendarManager.firstDayOfTheWeek);
// this.updateEvents();
}
componentWillUnmount(){
//取消订阅
subscription.remove();
}
render() {
return (
{JSON.stringify(this.state.events)}
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
textStyle: {
fontSize: 20,
}
})