react native中 js 与 ios 的值传递

之前做过 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代码尽量写的详细,尽让读者可以”抄”过去,简单改改就能用。

有疑问请在评论区留言

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享