一、简介

在前面介绍了很多ReactNative中UI组件和API组件,这些都是Facebook团队封装好的基础组件,开发者可以直接使用。然而,在实际的开发过程中,面对复杂的需求,此时原生的Native组件可能就无法满足要求了。当然,这种情况Facebook团队是当然考虑过了,所以在ReactNative开发中也支持开发者进行自定义API组件。

二、详解

1、类模块和方法

一个普通的OC类以及方法,并不会被系统处理成模块进而被调用。模块必须在编译以及运行时向系统注册,同时告诉系统什么属性和方法可以被JavaScript调用。自定义的OC模块类必须遵守RCTBridgeModule协议。RCTBridgeModule协议定义了一些模块的基本属性和方法以及一些宏命令。可以直接通过宏命令来告诉ReactNative需要注册的模块类和暴露的方法。注意JavaScript无法识别方法重载,所以定义方法时不要重名。 常用宏命令如下:

//1、注册模块类。可选的js_name参数将用作JS模块名称。如果省略,则JS模块名称将与Objective-C类名称匹配。
//注意:如果定义的类名包含RCT前缀,会被系统格式化去除。例如原生的RCTActionSheetManager被格式化成ActionSheetManager
//也即:var RCTActionSheetManager = require('NativeModules').ActionSheetManager
#define RCT_EXPORT_MODULE(js_name) //2、将OC中定义的模块方法暴露出来,method是OC方法
#define RCT_EXPORT_METHOD(method) //3、将OC中定义的模块方法暴露出来,method是OC方法,js_name是method的自定义名称
#define RCT_REMAP_METHOD(js_name, method) //4、这种方式通过“ NativeModules.MyModule.method”将MyModule和方法method同时公开给JavaScript。
//obj_name:模块类 objc_supername:模块类的父类 js_name: 模块类的别名
#define RCT_EXTERN_MODULE(objc_name, objc_supername)
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername)

对了模块类的注册,如果没有不用宏定义导出,也可以实现下面这个协议方法即可,它其实在宏定义RCT_EXPORT_MODULE中被实现。

// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName

JavaScript和Objective-C是两个完全不同的语言,所支持的数据类型也各有不同,如果之间需要通信,那必须完成数据类型的转换。ReactNative中双方的通信数据采用JSON类型来转换,因此支持标准JSON的类型都是支持的。如下所示:

//字符串类型
string (NSString) //数值类型
number (NSInteger, float, double, CGFloat, NSNumber) //布尔类型
boolean (BOOL, NSNumber) //数组类型
array (NSArray) //字典类型
map (NSDictionary) //block类型
function (RCTResponseSenderBlock) 

执行Native模块方法之前,RCTModuleMethod会根据Native方法定义的参数类型通过RCTConvert.h进行转换。在RCTConvert.h中,除了支持JSON标准类型外,也支持一系列常用类型。如下所示:

//这些类型都包括但不局限于一下类型(可以查看类RCTConvert.h)
NSDate、UIColor、UIFont、NSURL、NSURLRequest、UIImage....
UIColorArray、NSNumberArray、NSURLArray...
NSTextAlignment、NSUnderlineStyle....
CGPoint、CGSize.... //例如JavaScript中的date转换成OC的NSDate
date.toTime() => NSDate

2、回调函数

ReactNative中定义了几种类型的块函数作为回调函数,RCTModuleMethod以及MessageQueue会根据不同的类型来作对应的处理。Native中定义的回调函数会在执行时都会将数据传递给JavaScript环境,来执行对应的JavaScript函数。定义的几种回调函数如下所示:

//接收多个参数的回调函数,定义回调函数参数的顺序要和Native模块中传入的NSArray中的对象顺序保持一致,这样才能接收到正确的参数
typedef void (^RCTResponseSenderBlock)(NSArray *response) //接收错误参数的回调函数
typedef void (^RCTResponseErrorBlock)(NSError *error) //处理Promise Resolve的异步回调函数,支持then.函数式编程
typedef void (^RCTPromiseResolveBlock)(id result) //处理Promise Reject的异步回调函数,支持then.函数式编程
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error)

3、线程

JavaScript代码都是单线程运行的,而在Native模块中,线程问题自然而然需要被关注。在ReactNative中,所有的Native模块都默认在各自独立的GCD串行队列上。如果需要特别指定某个线程队列,可以通过-(dispatch_queue_t)methodQueue方法来实现,如下:

//给某一个异步线程自定义队列
-(dispatch_queue_t)methodQueue{
  return dispatch_queue_create("com.facebook.ReactNative.NameQueue", DISPATCH_QUEUE_SERIAL);

模块中所有的模块方法都会运行在同一线程队列中,如果某些方法需要单独指定队列,可以使用dispatch_async函数实现,如下:

//在thread方法内异步执行(都是异步线程,但是队列不同)
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
code excute...
});
}
)

注意:如果多个模块需要共享一个线程队列,那么需要手动缓存共享的队列实例,并在methodQueue中返回共享实例即可。而不是创建一个相同标签的实例。

4、常量导出

ReactNative还支持Native模块暴露一些常量数据供JavaScript模块方法使用。主要有这几种用法:

(1)Native组件中的常量值,例如版本和事件名称等;(2)Native中定义枚举在JavaScript中使用的对应值;(3)边界定义,例如控件允许的最小尺寸或者默认尺寸等。

常量的暴露通过方法constantsToExport方法实现,如下:

//1、OC中导出一个字典对象
-(NSDictionary *)constantsToExport {
return @{@"name": @"Zhangsan"};
}
//JavaScript中访问如下, Module为注册的模块类
Module.name //2、OC中定义一个枚举并导出
typedef NS_ENUM(NSInteger, MoveAnimation){
MoveAnimationNome,
MoveAnimationFade,
MoveAnimationSlide
}
-(NSDictionary *)constantsToExport{
return @{
@"MoveAnimationNome": @(MoveAnimationNome),
@"MoveAnimationFade": @(MoveAnimationFade),
@"MoveAnimationSlide": @(MoveAnimationSlide),
}
} //给RCTConvert类添加扩展,这样在模块方法调用中使用常量导出的枚举值,通信到Native中时,会从整型自动转换为定义的枚举类型
@implementation RCTConvert (MoveAnimation)
RCT_ENUM_CONVERTER(MoveAnimation,
(@{
@"MoveAnimationNome": @(MoveAnimationNome),
@"MoveAnimationFade": @(MoveAnimationFade),
@"MoveAnimationSlide": @(MoveAnimationSlide),
}), MoveAnimationNome, integerValue)
@end //JavaScript中访问如下
Module.updateMoveAnimation(Module.MoveAnimationSlide, callback);

5、事件

ReactNative在Native向JavaScript传递消息机制的基础上实现了一个非常低耦合的消息事件订阅系统,Native通过RCTEventDispatcher向JavaScript端的EventEmitter模块发送事件消息,由EventEmitter模块通知该事件的订阅者来执行事件的响应。在大多数场景下,只需要使用这种通知的方式间接完成Native对JavaScript的调用。如下:

//首先在JavaScript端对事件进行订阅,并且添加事件响应函数
const { NativeAppEventEmitter } = require('react-native');
let subscription = NativeAppEventEmitter.addListener('EventReminder', (reminder) => console.log(reminder.name) ) //在OC中,当在Native模块上发出事件通知时,EventEmitter模块则会执行所有注册EventReminder事件的响应函数
#import "RCTEventDispacther.h"
[self.bridge.eventDispacther sendAppEventWithName: @"EventReminder" body:@{@"name": eventName}]

ReactNative中定义了不同的接口以及接收者来区分事件的类型,注意在合适的时候需要手动取消事件的订阅,以避免内存泄露,类型如下所示:

//发送应用相关的事件,例如数据更新
//NativeAppEventEmitter
-(void)sendAppEventWithName:(NSString *)name body:(id)body //发送设备相关的事件,例如地理位置和屏幕旋转
//DeviceEventEmitter
-(void)sendDeviceEventWithName:(NSString *)name body:(id)body

三、使用

1、类模块

  • 首先在创建OC类,然后使用宏定义注册
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h> NS_ASSUME_NONNULL_BEGIN

//必须要实现RCTBridgeModule协议
@interface LoginManager : NSObject<RCTBridgeModule> @end NS_ASSUME_NONNULL_END
#import "LoginManager.h"

@implementation LoginManager

//下面的方式二选一,不可同时使用,不然就重复了,编译报错
//方式一 不传参数:导出模块类:默认名称为当前类名 LoginManager <===> RCT_EXPORT_MOUDLE(LoginManager)
RCT_EXPORT_MODULE(); //方式二 传参数:导出模块类:设置自定义的名称为 MyLoginManager
//RCT_EXPORT_MODULE(MyLoginManager); @end
  • 然后在ReactNative中导入,打印看模块类是否注册成功,如下所示:
//import { NativeModules } from 'react-native'
let NativeModules = require('NativeModules'); //import 或 require 方式都行
//下面结果显示了模块名为LoginManager,如果在注册时传入参数为自定义模块类名如MyLoginManager,则会显示MyLoginManager,使用模块类时也是MyLoginManager。
console.log(NativeModules);

2、模块方法

  • 首先在OC类中定义方法,然后使用宏定义暴露给模块
#import "LoginManager.h"

@implementation LoginManager

//导出模块类
RCT_EXPORT_MODULE(); //重映射,auth为code方法的新名称
RCT_REMAP_METHOD(auth,code:(NSString *)account{
NSLog(@"%s---获取验证----",__func__);
NSLog(@"account----%@",account);
}); //login为方法名
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password{
NSLog(@"%s---登录账号----",__func__);
NSLog(@"account----%@",account);
NSLog(@"password----%@",password);
}); //logout为方法名
RCT_EXPORT_METHOD(logout:(NSString *)account{
NSLog(@"%s---退出账号----",__func__);
NSLog(@"account----%@",account);
});
@end
  • 然后在ReactNative中,打印看模块类是否注册成功并调用,如下所示:
//导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //打印模块类
console.log("LoginManager",LoginManager); //调用模块类的方法
LoginManager.auth("xiayuanquan");
LoginManager.login("xiayuanquan", "");
LoginManager.logout("xiayuanquan");
2020-01-17 14:22:21.453 [info][tid:com.facebook.react.JavaScript] 'LoginManager', { auth: { [Function: fn] type: 'async' },
login: { [Function: fn] type: 'async' },
logout: { [Function: fn] type: 'async' } } [14:21:51] -[LoginManager code:] [第19行] -[LoginManager code:]---获取验证----
[14:21:51] -[LoginManager code:] [第20行] account----xiayuanquan [14:21:51] -[LoginManager login:password:] [第25行] -[LoginManager login:password:]---登录账号----
[14:21:51] -[LoginManager login:password:] [第26行] account----xiayuanquan
[14:21:51] -[LoginManager login:password:] [第27行] password----123456 [14:21:51] -[LoginManager logout:] [第32行] -[LoginManager logout:]---退出账号----
[14:21:51] -[LoginManager logout:] [第33行] account----xiayuanquan

3、回调函数

  • 在OC中定义带回调函数的方法
#import "LoginManager.h"

@implementation LoginManager

//导出模块类
RCT_EXPORT_MODULE(); //设置普通的回调函数
//fetchUserInfoWithToken为方法名,successCallback为成功回调,failureCallback为失败回调
RCT_EXPORT_METHOD(fetchUserInfoWithToken:(NSString *)token success:(RCTResponseSenderBlock)successCallback failure:(RCTResponseErrorBlock)failureCallback
{
if(token.length> && successCallback){
successCallback(@[
@"account = xiayuanquan",
@"password = 123456",
[NSString stringWithFormat:@"token = %@",token]
]);
}
else{
if(failureCallback){
failureCallback(
[[NSError alloc] initWithDomain:NSOSStatusErrorDomain
code:
userInfo: @{NSLocalizedDescriptionKey: @"token exception"}]
);
}
}
}); //设置异步处理的回调函数
//sendMessage为方法名,successCallback为成功回调,failureCallback为失败回调
RCT_EXPORT_METHOD(sendMessage:(NSString *)message success:(RCTPromiseResolveBlock)successCallback failure:(RCTPromiseRejectBlock)failureCallback
{
if(message.length> && successCallback){
successCallback(@"发送成功!");
}
else{
if(failureCallback){
failureCallback(@"",@"发送失败",nil);
}
}
}); @end
  • 在ReactNative中,调用该函数,打印回调函数结果
//导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //接收普通回调函数
//传入正确的token,触发成功回调
LoginManager.fetchUserInfoWithToken("xyakajsd121jdsjd", (account, password, token)=>{
console.log(account + ", " +password + ", "+token);
}, (error) => {
console.log("error code: " +error.code);
Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
}); //传入错误的token,触发失败回调
LoginManager.fetchUserInfoWithToken("", (account, password, token)=>{
console.log(account + ", " +password + ", "+token);
}, (error) => {
console.log("error code: " +error.code);
Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
}); //---------------------------------------------------------------------------------------// //处理Promise回调函数
//发送消息成功,触发成功回调
LoginManager.sendMessage("Hello world")
.then((message) => { console.log("message-----"+message) })
.catch((error) => { console.log("error----"+error.code+", " + error.message) }); //发送消息失败,触发成功回调
LoginManager.sendMessage("")
.then((message) => { console.log("message-----"+message) })
.catch((error) => { console.log("error----"+error.code +", " + error.message) });
2020-01-17 15:30:03.594 [info][tid:com.facebook.react.JavaScript] account = xiayuanquan, password = 123456, token = xyakajsd121jdsjd
2020-01-17 15:30:03.599 [info][tid:com.facebook.react.JavaScript] error code: ENSOSSTATUSERRORDOMAIN404
2020-01-17 15:30:03.600 [info][tid:com.facebook.react.JavaScript] 'NSLocalizedDescription', 'token exception' 2020-01-17 15:30:03.603 [info][tid:com.facebook.react.JavaScript] message-----发送成功!
2020-01-17 15:30:03.611 [info][tid:com.facebook.react.JavaScript] error----300, 发送失败

4、常量导出

  • 在OC中定义常量并导出
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h> NS_ASSUME_NONNULL_BEGIN //OC中定义一个枚举常量
typedef NS_ENUM(NSInteger, MoveDiretion){
MoveDiretionNone,
MoveDiretionLeft,
MoveDiretionRight,
MoveDiretionBottom,
MoveDiretionTop
}; @interface LoginManager : NSObject<RCTBridgeModule> @end
NS_ASSUME_NONNULL_END
#import "LoginManager.h"

@implementation LoginManager

//导出模块类
RCT_EXPORT_MODULE(); //重写constantsToExport, 枚举常量导出
- (NSDictionary<NSString *, id> *)constantsToExport {
return @{
@"MoveDiretionNone": @(MoveDiretionNone),
@"MoveDiretionLeft": @(MoveDiretionLeft),
@"MoveDiretionRight": @(MoveDiretionRight),
@"MoveDiretionBottom": @(MoveDiretionBottom),
@"MoveDiretionTop": @(MoveDiretionTop)
};
} //定义一个移动方法,根据传入的枚举值移动
//move为方法名
RCT_EXPORT_METHOD(move:(MoveDiretion)moveDiretion{
switch(moveDiretion){
case MoveDiretionNone:
NSLog(@"仍保持原始位置 --- MoveDiretionNome");
break;
case MoveDiretionLeft:
NSLog(@"向左边移动位置 --- MoveDiretionLeft");
break;
case MoveDiretionRight:
NSLog(@"向右边移动位置 --- MoveDiretionRight");
break;
case MoveDiretionBottom:
NSLog(@"向下边移动位置 --- MoveDiretionBottom");
break;
case MoveDiretionTop:
NSLog(@"向上边移动位置 --- MoveDiretionTop");
break;
}
}); @end
  • 给RCTConvert创建分类,进行类型转换
#import "RCTConvert+MoveDiretion.h"
#import "LoginManager.h" @implementation RCTConvert (MoveDiretion) //给RCTConvert类添加扩展,这样在模块方法调用中使用常量导出的枚举值,通信到Native中时,会从整型自动转换为定义的枚举类型
RCT_ENUM_CONVERTER(MoveDiretion,(@{
@"MoveDiretionNone": @(MoveDiretionNone),
@"MoveDiretionLeft": @(MoveDiretionLeft),
@"MoveDiretionRight": @(MoveDiretionRight),
@"MoveDiretionBottom": @(MoveDiretionBottom),
@"MoveDiretionTop": @(MoveDiretionTop),
}), MoveDiretionNone, integerValue) @end
  • 在ReactNative中,访问常量,打印结果
//导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //访问模块常量
LoginManager.move(LoginManager.MoveDiretionNone);
LoginManager.move(LoginManager.MoveDiretionLeft);
LoginManager.move(LoginManager.MoveDiretionRight);
LoginManager.move(LoginManager.MoveDiretionBottom);
LoginManager.move(LoginManager.MoveDiretionTop);
[16:13:53] -[LoginManager move:] [第90行] 仍保持原始位置 --- MoveDiretionNome
[16:13:53] -[LoginManager move:] [第93行] 向左边移动位置 --- MoveDiretionLeft
[16:13:53] -[LoginManager move:] [第96行] 向右边移动位置 --- MoveDiretionRight
[16:13:53] -[LoginManager move:] [第99行] 向下边移动位置 --- MoveDiretionBottom
[16:13:53] -[LoginManager move:] [第102行] 向上边移动位置 --- MoveDiretionTop

5、线程

  • 在OC中重写methodQueue协议方法,给当前模块类指定自定义的串行队列
#import "LoginManager.h"

@implementation LoginManager

//导出模块类
RCT_EXPORT_MODULE(); //可以重写队列,给当前模块类指定自定义的串行队列。若不指定,则系统默认会给当前模块类随机分配一个串行队列。
//该方法重写后,当前模块类的所有方法都会在这个自定义的串行队列中异步执行。除非开发者在方法体内手动切换其他线程。
-(dispatch_queue_t)methodQueue{
return dispatch_queue_create("com.facebook.ReactNative.LoginManagerQueue", DISPATCH_QUEUE_SERIAL);
} //定义一个方法,获取线程和队列信息
//thread为方法名
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{ const char *queueName = dispatch_queue_get_label([self methodQueue]);
NSLog(@"当前线程1 ------- %@-----%s", [NSThread currentThread], queueName); if(newQueue){ dispatch_async(dispatch_get_main_queue(), ^{
const char *queueName2 = dispatch_queue_get_label(dispatch_get_main_queue());
NSLog(@"当前线程2 ------- %@ -------%s", [NSThread currentThread], queueName2);
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
const char *queueName3 = dispatch_queue_get_label(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ));
NSLog(@"当前线程3 ------- %@-------%s", [NSThread currentThread], queueName3);
});
}
}); @end
  • 在ReactNative中,访问线程和队列名称,打印结果(name为null均为子线程,也即异步)
//导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //访问当前线程
LoginManager.thread(true);
[17:10:08] -[LoginManager thread:] [第118行] 当前线程1 ------- <NSThread: 0x281e3a0c0>{number = 8, name = (null)}-----com.facebook.ReactNative.LoginManagerQueue

[17:10:08] -[LoginManager thread:]_block_invoke [第124行] 当前线程2 ------- <NSThread: 0x281357f00>{number = 1, name = main} -------com.apple.main-thread

[17:10:08] -[LoginManager thread:]_block_invoke_2 [第129行] 当前线程3 ------- <NSThread: 0x281e3a0c0>{number = 8, name = (null)}-------com.apple.root.default-qos

6、事件

  • 在OC中添加监听事件,监控屏幕旋转状态
#import "LoginManager.h"

@implementation LoginManager

//导出模块类
RCT_EXPORT_MODULE(); //初始化, 添加屏幕旋转监听者
-(instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
                           selector:@selector(orientationDidChange:)
                            name:UIDeviceOrientationDidChangeNotification object:nil];
}
return self;
} //获取当前屏幕的尺寸
static NSDictionary *Dimensions(){
CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height);
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
CGFloat scale = RCTScreenScale();
if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
width = MAX(RCTScreenSize().width, RCTScreenSize().height);
height = MIN(RCTScreenSize().width, RCTScreenSize().height);
}
return @{
@"width": @(width),
@"height": @(height),
@"scale": @(scale)
};
}
//定义一个方法,获取屏幕信息
//getDimensions为方法名
RCT_EXPORT_METHOD(getDimensions:(RCTResponseSenderBlock)callback{
if (callback) {
callback(@[[NSNull null], Dimensions()]);
}
}); //监听方法,使用RCTEventDispatcher的eventDispatcher调用sendDeviceEventWithName函数发送事件信息
//可以将事件名称作为常量导出,提供给ReactNative中使用,也即添加到constantsToExport方法中的字典中即可。类似上面的枚举。
@synthesize bridge = _bridge;
-(void)orientationDidChange:(NSNotification *)notification {
[_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange" body:@{
@"orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape": @"Portrait",
@"Dimensions": Dimensions()}
];
} //移除监听者
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
} @end
  • 在ReactNative中,添加事件订阅,获取屏幕宽度方向信息
//导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //访问屏幕信息
LoginManager.getDimensions( (error, dimensions) => {
Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
}); //订阅事件,监听屏幕旋转
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
let subscription = RCTDeviceEventEmitter.addListener('orientationDidChange', (dimensions) => {
Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});
//subscription.remove();
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'
2020-01-17 18:20:03.234 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'width', 414
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'scale', 3
2020-01-17 18:20:03.235 [info][tid:com.facebook.react.JavaScript] 'height', 736 2020-01-17 18:20:03.238 [info][tid:com.facebook.react.JavaScript] Running application "App" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF 2020-01-17 18:20:24.245 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 736, scale: 3, height: 414 }
2020-01-17 18:20:24.245 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Landscape'
2020-01-17 18:20:25.407 [info][tid:com.facebook.react.JavaScript] 'Dimensions', { width: 414, scale: 3, height: 736 }
2020-01-17 18:20:25.408 [info][tid:com.facebook.react.JavaScript] 'orientation', 'Portrait'

四、完整代码

OC类:

LoginManager.h

//
// LoginManager.h
// RNDemo
//
// Created by 夏远全 on 2020/1/16.
// Copyright © 2020 Facebook. All rights reserved.
// #import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTUtils.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTBridgeModule.h> NS_ASSUME_NONNULL_BEGIN //OC中定义一个枚举并导出
typedef NS_ENUM(NSInteger, MoveDiretion){
MoveDiretionNone,
MoveDiretionLeft,
MoveDiretionRight,
MoveDiretionBottom,
MoveDiretionTop
}; @interface LoginManager : NSObject<RCTBridgeModule> @end NS_ASSUME_NONNULL_END

LoginManager.m

//
// LoginManager.m
// RNDemo
//
// Created by 夏远全 on 2020/1/16.
// Copyright © 2020 Facebook. All rights reserved.
// #import "LoginManager.h" @implementation LoginManager //初始化, 添加屏幕旋转监听者
-(instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
return self;
} //导出模块类
RCT_EXPORT_MODULE(); //重映射,auth为code方法的新名称
RCT_REMAP_METHOD(auth,code:(NSString *)account{
NSLog(@"%s---获取验证----",__func__);
NSLog(@"account----%@",account);
}); //login为方法名
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password{
NSLog(@"%s---登录账号----",__func__);
NSLog(@"account----%@",account);
NSLog(@"password----%@",password);
}); //logout为方法名
RCT_EXPORT_METHOD(logout:(NSString *)account{
NSLog(@"%s---退出账号----",__func__);
NSLog(@"account----%@",account);
}); //设置普通的回调函数
//fetchUserInfoWithToken为方法名,successCallback为成功回调,failureCallback为失败回调
RCT_EXPORT_METHOD(fetchUserInfoWithToken:(NSString *)token success:(RCTResponseSenderBlock)successCallback failure:(RCTResponseErrorBlock)failureCallback
{
if(token.length> && successCallback){
successCallback(@[
@"account = xiayuanquan",
@"password = 123456",
[NSString stringWithFormat:@"token = %@",token]
]);
}
else{
if(failureCallback){
failureCallback(
[[NSError alloc] initWithDomain:NSOSStatusErrorDomain
code:
userInfo: @{NSLocalizedDescriptionKey: @"token exception"}]
);
}
}
}); //设置异步处理的回调函数
//sendMessage为方法名,successCallback为成功回调,failureCallback为失败回调
RCT_EXPORT_METHOD(sendMessage:(NSString *)message success:(RCTPromiseResolveBlock)successCallback failure:(RCTPromiseRejectBlock)failureCallback
{
if(message.length> && successCallback){
successCallback(@"发送成功!");
}
else{
if(failureCallback){
failureCallback(@"",@"发送失败",nil);
}
}
}); //重写constantsToExport, 枚举常量导出
- (NSDictionary<NSString *, id> *)constantsToExport {
return @{
@"MoveDiretionNone": @(MoveDiretionNone),
@"MoveDiretionLeft": @(MoveDiretionLeft),
@"MoveDiretionRight": @(MoveDiretionRight),
@"MoveDiretionBottom": @(MoveDiretionBottom),
@"MoveDiretionTop": @(MoveDiretionTop)
};
} //定义一个移动方法,根据传入的枚举值移动
//move为方法名
RCT_EXPORT_METHOD(move:(MoveDiretion)moveDiretion{
switch(moveDiretion){
case MoveDiretionNone:
NSLog(@"仍保持原始位置 --- MoveDiretionNome");
break;
case MoveDiretionLeft:
NSLog(@"向左边移动位置 --- MoveDiretionLeft");
break;
case MoveDiretionRight:
NSLog(@"向右边移动位置 --- MoveDiretionRight");
break;
case MoveDiretionBottom:
NSLog(@"向下边移动位置 --- MoveDiretionBottom");
break;
case MoveDiretionTop:
NSLog(@"向上边移动位置 --- MoveDiretionTop");
break;
}
}); //可以重写队列,给当前模块类指定自定义的串行队列。若不指定,则系统默认会给当前模块类随机分配一个串行队列。
//这个方法一旦重写。当前模块的所有方法均会在该自定义的串行队列中异步执行
-(dispatch_queue_t)methodQueue{
return dispatch_queue_create("com.facebook.ReactNative.LoginManagerQueue", DISPATCH_QUEUE_SERIAL);
} //定义一个方法,获取线程和队列信息
//thread为方法名
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{ const char *queueName = dispatch_queue_get_label([self methodQueue]);
NSLog(@"当前线程1 ------- %@-----%s", [NSThread currentThread], queueName); if(newQueue){ dispatch_async(dispatch_get_main_queue(), ^{
const char *queueName2 = dispatch_queue_get_label(dispatch_get_main_queue());
NSLog(@"当前线程2 ------- %@ -------%s", [NSThread currentThread], queueName2);
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
const char *queueName3 = dispatch_queue_get_label(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ));
NSLog(@"当前线程3 ------- %@-------%s", [NSThread currentThread], queueName3);
});
}
}); //获取当前屏幕的尺寸
static NSDictionary *Dimensions(){
CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height);
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
CGFloat scale = RCTScreenScale();
if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
width = MAX(RCTScreenSize().width, RCTScreenSize().height);
height = MIN(RCTScreenSize().width, RCTScreenSize().height);
}
return @{
@"width": @(width),
@"height": @(height),
@"scale": @(scale)
};
}
//定义一个方法,获取屏幕信息
//getDimensions为方法名
RCT_EXPORT_METHOD(getDimensions:(RCTResponseSenderBlock)callback{
if (callback) {
callback(@[[NSNull null], Dimensions()]);
}
}); //监听方法,使用RCTEventDispatcher的eventDispatcher调用sendDeviceEventWithName函数发送事件信息
//可以将事件名称作为常量导出,提供给ReactNative中使用,也即添加到constantsToExport方法中的字典中即可。类似上面的枚举。
@synthesize bridge = _bridge;
-(void)orientationDidChange:(NSNotification *)notification {
[_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange" body:@{
@"orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape": @"Portrait",
@"Dimensions": Dimensions()}
];
} //移除监听者
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
} @end

RCTConvert+MoveDiretion.h

//
// RCTConvert+MoveDiretion.h
// RNDemo
//
// Created by 夏远全 on 2020/1/17.
// Copyright © 2020 Facebook. All rights reserved.
// #import <React/RCTConvert.h> NS_ASSUME_NONNULL_BEGIN @interface RCTConvert (MoveDiretion) @end NS_ASSUME_NONNULL_END

RCTConvert+MoveDiretion.m

//
// RCTConvert+MoveDiretion.m
// RNDemo
//
// Created by 夏远全 on 2020/1/17.
// Copyright © 2020 Facebook. All rights reserved.
// #import "RCTConvert+MoveDiretion.h"
#import "LoginManager.h" @implementation RCTConvert (MoveDiretion) //给RCTConvert类添加扩展,这样在模块方法调用中使用常量导出的枚举值,通信到Native中时,会从整型自动转换为定义的枚举类型
RCT_ENUM_CONVERTER(MoveDiretion,(@{
@"MoveDiretionNone": @(MoveDiretionNone),
@"MoveDiretionLeft": @(MoveDiretionLeft),
@"MoveDiretionRight": @(MoveDiretionRight),
@"MoveDiretionBottom": @(MoveDiretionBottom),
@"MoveDiretionTop": @(MoveDiretionTop),
}), MoveDiretionNone, integerValue) @end

PrefixHeader.pch

//
// PrefixHeader.h
// RNDemo
//
// Created by 夏远全 on 2020/1/16.
// Copyright © 2020 Facebook. All rights reserved.
// #ifndef PrefixHeader_h
#define PrefixHeader_h #ifdef DEBUG #define NSLog(format, ...) printf("[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]); #else #define NSLog(format, ...) #endif #endif /* PrefixHeader_h */

ReactNative中:

wrapper-rn-api.js

import React, { Component } from 'react';
import {
View
} from 'react-native'; //导入模块
import { NativeModules } from 'react-native'; //模块类
const LoginManager = NativeModules.LoginManager; //打印模块类
console.log("LoginManager",LoginManager); // 调用模块类的方法
LoginManager.auth("xiayuanquan");
LoginManager.login("xiayuanquan", "");
LoginManager.logout("xiayuanquan"); //接收普通回调函数
LoginManager.fetchUserInfoWithToken("xyakajsd121jdsjd", (account, password, token)=>{
console.log(account + ", " +password + ", "+token);
}, (error) => {
console.log("error code: " +error.code);
Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
}); LoginManager.fetchUserInfoWithToken("", (account, password, token)=>{
console.log(account + ", " +password + ", "+token);
}, (error) => {
console.log("error code: " +error.code);
Object.keys(error.userInfo).forEach(key => console.log(key, error.userInfo[key]));
}); //处理Promise回调函数
LoginManager.sendMessage("Hello world")
.then((message) => { console.log("message-----"+message) })
.catch((error) => { console.log("error----"+error.code+", " + error.message) }); LoginManager.sendMessage("")
.then((message) => { console.log("message-----"+message) })
.catch((error) => { console.log("error----"+error.code +", " + error.message) }); //访问模块常量
LoginManager.move(LoginManager.MoveDiretionNone);
LoginManager.move(LoginManager.MoveDiretionLeft);
LoginManager.move(LoginManager.MoveDiretionRight);
LoginManager.move(LoginManager.MoveDiretionBottom);
LoginManager.move(LoginManager.MoveDiretionTop); //访问当前线程
LoginManager.thread(true); //事件
LoginManager.getDimensions( (error, dimensions) => {
Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
}); //订阅事件
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
let subscription = RCTDeviceEventEmitter.addListener('orientationDidChange', (dimensions) => {
Object.keys(dimensions).forEach(key => console.log(key, dimensions[key]));
});
//subscription.remove(); export default class CustomRNComponent extends Component{
render (){
return <View/>
}
}

ReactNative: 自定义ReactNative API组件的更多相关文章

  1. ReactNative: 创建自定义List列表组件

    一.介绍 在App中,很多数据消息显示都是一行行动态展示的,例如新闻标题,其实每一条新闻标题都可以独立成一个简单的列表组件,之前我们使用Text组件将数据都写死了,为了提高组件的灵活性,我们可以使用T ...

  2. react-native自定义Modal模态框|仿ios、微信弹窗RN版

    前序 纵观每个优质项目,无论web端还是native原生应用开发,弹窗都是不可忽视的一环,能很大程度上直接决定用户体验.如:微信.支付宝.ios都有很成熟的一套弹窗UI展示场景. 最近一直沉迷在rea ...

  3. ReactNative: 使用Text文本组件

    一.简言 初学RN,一切皆新.Text组件主要用于显示文本,Text组件的重要性不言而喻,无论是Web开发还是客户端开发,都离不开它.它具有响应特性,也即表现为当它被触摸时是否显示为高亮状态.在Web ...

  4. React-Native之轮播组件looped-carousel的介绍与使用

    React-Native之轮播组件looped-carousel的介绍与使用 一,关于react-native轮播组件的介绍与对比 1,react-native-swiper在动态使用网页图片,多张图 ...

  5. React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发

    React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发   2016/09/23 |  React Native技术文章 |  Sky丶清|  4 条评论 |  1 ...

  6. Kubernetes 学习23 kubernetes资源指标API及自定义指标API

    一.概述 1.上集中我们说到,官方文档提示说从k8s 1.11版本开始,将监控体系指标数据获取机制移向新一代的监控模型.也就意味着对于我们的k8s来讲现在应该有这样两种资源指标被使用.一种是资源指标, ...

  7. GrapeCity Documents for Excel 文档API组件 V2.2 新特性介绍

    GrapeCity Documents for Excel 文档API组件 V2.2 正式发布,本次新版本包含诸多重量级产品功能,如:将带有形状的电子表格导出为 PDF.控制分页和电子表格内容.将Ex ...

  8. iOS开发之自定义表情键盘(组件封装与自动布局)

    下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自 ...

  9. Vue组件之自定义表单组件

    今天又看了一遍vue的文档,记得之前学习的时候,官方文档中有提过,v-model指令是一个语法糖,做两件事,一个是给表单控件元素绑定value,第二个是当输入时更新绑定的值,不过后来在"表单 ...

随机推荐

  1. (二)Centos7下Yum更新安装PHP5.5,5.6,7.0

    yum源默认的版本太低了,手动安装有一些麻烦,想采用Yum更新安装的可以使用下面的方案: 1.检查当前安装的PHP包 yum list installed | grep php 如果有安装的PHP包, ...

  2. H3C 最大跳数16导致网络尺度小

  3. php 变量名前加一个下划线含义

    https://segmentfault.com/q/1010000006467833 一个下划线是私有变量以及私有方法两个下划线是PHP内置变量. 以下划线开头,表示为类的私有成员. 这只是个不成文 ...

  4. UVa 10603 Fill [暴力枚举、路径搜索]

    10603 Fill There are three jugs with a volume of a, b and c liters. (a, b, and c are positive intege ...

  5. dotnet 通过 WMI 获取系统安装软件

    本文告诉大家如何通过 WMI 获取系统安装的软件,这个方法不能获取全部的软件 通过 Win32_Product 可以获取系统安装的软件 var mc = "Win32_Product&quo ...

  6. H3C MSTP

  7. 修改github上的项目语言类型

    当在github上上传一个项目时,可能会出现一个问题就是项目代码类型是自动生成的,可能与我们实际项目代码种类不匹配,此时就需要修改项目语言类型了. 由于无法直接更改,所以用到此方法: 在你的项目根目录 ...

  8. 【39.68%】【CF 714 C】Filya and Homework

    time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...

  9. maven仓库总结,maven私服搭建,批量mvn eclipse:eclipse

    配置pom.xml依赖包时在这里找包的描述: http://search.maven.org/#browse 以java为根目录. mvn archtype:generate -DgroupId=zt ...

  10. CKEditor配置,最适合新手两种方式详解。

    CKEditor.js的配置,大概有两种方式,这里有基础版和全面的版本可以试验 https://cdn.ckeditor.com/4.8.0/full-all/ckeditor.js http://c ...