之前做过 react native 相关的app开发工作,也做了一些比较有趣的事情。一直想写些东西记录一下,本次计划写三篇入门级的文章,在同一个 demo 中,运行在 ios 端。
代码:DEMO
// 运行demo
$ git clone https://github.com/lianglei777/demos.git
$ cd demos
$ git checkout RNFaceDemo
$ cd RNFaceDemo
$ npm install
$ cd ios
$ pod install
复制代码
环境搭建
这里主要说一下我环境搭建的时候遇到的问题
react-native 基础环境搭建可以参考 官网
cocoapods
ios的 cocoapods 安装以及相关命令 pod install 可能需要科学上网,不然大概率会失败,也可以考虑换源,方法如下
$ cd ~/.cocoapods/repos
$ pod repo remove master
<!-- 清华源 -->
$ git clone https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git master
<!-- 北京外国语大学,我目前在用的 -->
$ git clone https://mirrors.bfsu.edu.cn/git/CocoaPods/Specs.git master
<!-- gitclub -->
$ git clone https://gitclub.cn/CocoaPods/Specs.git master
复制代码
目前开发的环境
在安装环境的的时候,我确实感觉到了不同mac系统版本,带来的差异(以前的demo运行不起来了),这里贴一下目前的开发环境
macOS: 12.0 Beta版 Monterey
Node: 14.16.0
Xcode: 12.5
cocoapods:1.10.1
复制代码
PS:我觉得 react native 的开发环境搭建对新手来说也是一道坎,特别是没有 iOS 或者 android 开发经验的,请务必仔细阅读 官网 的教程。
js 主动传参调用 ios 原生定义的方法,并且返回参数到 js
ios 项目下 创建 RNManager 类文件,react native 原生桥接的组件和方法,默认文件名都需要以 Manager 结尾。文件内容如下:
RNManager.h
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTBridgeModule.h>
// 1. 必须继承 RCTBridgeModule 协议,并且导入必要的头文件
@interface RNManager : NSObject<RCTBridgeModule>
@end
复制代码
RNManager.m
#import "RNManager.h"
#import "AppDelegate.h"
@implementation RNManager
// 2. .m文件中添加此方法,才能将类暴露给 RN
RCT_EXPORT_MODULE();
// 3. 此方法是决定桥接的方法运行在那个线程上,一般是主线程,如果涉及到UI变化,那么久必须是主线程
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
// 4. RCT_REMAP_METHOD 就是实现桥接的方法
// sendMegToNative: 在rn中使用的方法名称
// one,two,three,successCallBack,errorCallBack 是传参
// 类似sendMegToNative(one,two,three, (success) => {}, (error) => {}),
// 普通类型需要使用ios写法,对应js就是 字符串、数字、bool等。传参是方法 需要是 RCTResponseSenderBlock 类型, 回调参数必须使用 数组 包裹,内容类型参考 iOS 语法。
// 如果想在使用 promise 类型的回调方法,可以使用 RCTPromiseResolveBlock、RCTPromiseRejectBlock 类型, 注意: callback 和 promise 不能混用,
RCT_REMAP_METHOD(sendMegToNative, :(NSString *)one :(NSString *)two :(NSString *)three :(RCTResponseSenderBlock)successCallBack :(RCTResponseSenderBlock)errorCallBack ){
NSString *title = one;
NSString *message = two;
NSString *cancelButtonTitle = @"取消";
NSString *otherButtonTitle = @"确定";
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
if(!errorCallBack){
return;
}
// 回调字符串
errorCallBack(@[@"我不学"]);
}];
UIAlertAction *otherAction = [UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if(!successCallBack){
return;
}
// 回调对象类型
successCallBack(@[@{@"result" : @"连夜学"}]);
}];
[alertController addAction:cancelAction];
[alertController addAction:otherAction];
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate.window.rootViewController presentViewController:alertController animated:YES completion:nil];
}
@end
复制代码
注意点请关注上面的 1、2、3、4 点。具体的方法实现就是正常的 object-c 语法。下面是 js 中调用
FunctionBridgeDemo.js
import React, {PureComponent} from 'react';
import {
View,
Button,
StyleSheet,
NativeModules,
} from 'react-native';
// 此处 RNManager 就是 ios中桥接的 RNManager 对象名称
const { RNManager } = NativeModules;
export default class FunctionBridgeDemo extends PureComponent {
sendMsgToNative = () => {
// 调用原生桥接的 sendMegToNative 的方法,传参、回调
RNManager.sendMegToNative(
'提醒一下',
'要学RN吗?',
'333',
result => {
console.log('success 返回 ==》', result);
},
error => {
console.log('error 返回 ==》', error);
},
);
};
render() {
return (
<View style={styles.container}>
<Button title="Hello Native" onPress={this.sendMsgToNative} />
</View>
);
}
}
复制代码
以上就是正常的 react native 中原生封装方法,在 js 层调用,如果想了解 promise 的封装方法,详见 demo 中相关方法的注释部分。
原生主动发送消息给 js
通过封装监听事件来实现
ios项目下创建 EventEmitManager 类文件。
EventEmitManager.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
// 继承 RCTEventEmitter类 和 RCTBridgeModule 协议
@interface EventEmitManager : RCTEventEmitter<RCTBridgeModule>
@end
复制代码
EventEmitManager.m
#import "EventEmitManager.h"
// js 层需要监听通知时的key
#define NATIVE_TO_RN_ONNOFIFICATION @"onNotification"
// 原生发送通知时的key
#define NATIVE_ONNOFIFICATION @"native_onNotification"
@implementation EventEmitManager{
bool hasListeners;
}
// 导出模块
RCT_EXPORT_MODULE()
// 暴露给js监听的key
-(NSArray*)supportedEvents {
return@[NATIVE_TO_RN_ONNOFIFICATION];
}
- (void)nativeSendNotificationToRN:(NSNotification*)notification {
NSLog(@"NativeToRN notification.userInfo = %@", notification.userInfo);
if (hasListeners) {
[self sendEventWithName:NATIVE_TO_RN_ONNOFIFICATION body:notification.userInfo];
}
}
// 重写 startObserving,js 开始监听时,原生实现监听方法
- (void)startObserving {
hasListeners = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nativeSendNotificationToRN:) name:NATIVE_ONNOFIFICATION object:nil];
}
// 重写 stopObserving, js 删除监听时,原生删除相应的监听方法
- (void)stopObserving {
hasListeners = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self name:NATIVE_ONNOFIFICATION object:nil];
}
@end
复制代码
以上就定义了一个监听的方法类,可以发起监听和删除监听,相关内容都添加了注释,要注意其中监听的两个 key 字段,那如何去使用呢。
可以在ios项目的 AppDelegate.m 的声明周期方法中添加,相应的触发方法,如下
AppDelegate.m
...
// 前台进后台
- (void)applicationWillResignActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:@"native_onNotification" object:nil userInfo:@{@"lifeState": @"WILL_GO_BACKGROUD"}];
}
...
复制代码
js 中实现
FunctionBridgeDemo.js
const eventEmitManagerEmitter = new NativeEventEmitter(EventEmitManager);
...
this.subscription = eventEmitManagerEmitter.addListener(
'onNotification',
reminder => {
console.log('监听 ==》', reminder);
},
);
...
componentWillUnmount() {
this.subscription && this.subscription.remove();
}
复制代码
在 app 前台 进入后台时,会有相关监听的 log,一定要注意 key 的区分,详细内容请查看demo.
小结
上述的两种传值方法其实在官网都能找到 位置, 但是刚接触 RN 的同学,一般都只熟悉 原生 和 js 中的一种,这有时候就会有理解上的困惑,而且官网例子有时候又”不够详细”,照着官网写代码的时候无法达到想要的效果,比如第二点监听的写法,看官网写法,并不能应用到实际中来(最起码我是这样)。所以我demo代码尽量写的详细,尽让读者可以”抄”过去,简单改改就能用。
有疑问请在评论区留言