原生与React Native通信完整指南

Viewed 0

React Native(RN)与原生代码之间的通信是混合开发中的关键部分。本文将详细阐述原生与RN页面相互跳转、方法间的相互调用、以及H5页面调用原生页面进而调用RN页面等通信机制。

一、原生与RN通信

首先进行准备工作:通过 react-native init 创建一个RN新项目,得到带有 iosandroid 目录的文件夹。将这两个目录下的文件替换为自己的项目文件。接着修改Podfile文件,引入RN所需的库。示例如下:

pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/'
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'

pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

1. 原生跳RN页面

RCTRootView 是一个可以将RN视图封装到原生组件中并提供接口的UIView容器。通过类型为 NSDictionaryinitialProperties 可以将任意属性传递给RN应用,这些属性会在RN内部转化为JSON对象。

首先创建RN的桥接管理类(单例)实现 RCTBridgeDelegate 协议。在.h文件中导入必要的头文件:

#import <React/RCTBridgeModule.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTBridgeDelegate.h>

@interface XXXRCTManager : NSObject<RCTBridgeDelegate>
+ (instancetype)shareInstance;
@property (nonatomic, readonly, strong) RCTBridge *bridge;
@end

在.m文件中实现单例和桥初始化:

static XXXRCTManager *_instance = nil;
+ (instancetype)shareInstance{
    if (_instance == nil) {
        _instance = [[self alloc] init];
    }
    return _instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    if (_instance == nil) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [super allocWithZone:zone];
        });
    }
    return _instance;
}

-(instancetype)init{
    if (self = [super init]) {
        _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
    }
    return self;
}

实现 sourceURLForBridge 方法,在调试模式下读取 index 文件资源,打包时读取 jsbundle 资源:

#pragma mark - RCTBridgeDelegate
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
# if DEBUG
        return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"
                                                              fallbackResource:nil];
# else
    return [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"jsbundle"];
#endif
}

然后创建容纳RN页面的控制器。在.h文件中定义:

@interface XXXReactHomeViewController : UIViewController
@property(nonatomic,strong)NSString *rnPath; // 传递给RN的数据 页面名称
@end

在.m文件中初始化 RCTRootView 并添加到控制器页面上:

NSDictionary *props = @{@"path" : self.rnPath};
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:[SZLRCTManager shareInstance].bridge                                                     moduleName:@"RN中AppRegistry注册的名字"                                              initialProperties:props];

这样,iOS页面就能跳转到RN项目的首页了。

2. RN页面跳原生页面及调用原生方法

RCTBridgeModule 是定义好的protocol,实现该协议的类会自动注册到Bridge中。所有实现 RCTBridgeModule 的类都必须包括宏 RCT_EXPORT_MODULE(),用于自动注册Module。

首先新建类实现 RCTBridgeModule 协议。在.h文件中:

@interface xxxModule : NSObject<RCTBridgeModule>
@end

在.m文件中导出方法:

RCT_EXPORT_METHOD(goBack){
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"configBack" object:nil];
    });
}

在承载RN页面的控制器中接收通知,并实现从RN返回到原生页面的方法:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(navagateBack) name:@"configBack" object:nil];
- (void)navagateBack{
    [self.navigationController popViewControllerAnimated:YES];
}

在RN界面中,通过 NativeModules 引入原生的module类,并调用返回原生界面的方法:

import {
  NativeModules,
} from 'react-native';
onPressBack={() => {
        NativeModules.xxxModule.goBack();
        }}

3. 将原生参数传递给RN

通过 RCT_EXPORT_METHOD 可以将原生的参数传递给RN,或让RN实现原生的操作。例如,在原生端定义方法:

// 获取token
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getToken)
{
    NSString *token = [[NSUserDefaults standardUserDefaults]objectForKey:@"token"];
    return token;
}
// 退出登录
RCT_EXPORT_METHOD(signOut){
    dispatch_async(dispatch_get_main_queue(), ^{
        AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        LoginViewController *loginVC = [[LoginViewController alloc]init];
        UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:loginVC];
        appDelegate.window.rootViewController = nav;
    });
}

在RN方调用:

import { NativeModules } from 'react-native';
// 拿token
requestObj.headers.Authorization = NativeModules.config.getToken();
// 调用原生的退出登录方法
NativeModules.XXXModule.signOut();

4. 多入口跳转到RN不同的页面

要从不同的原生页面进入不同的RN页面,可以通过在初始化 RCTRootView 时传递不同路径参数实现。在需要跳转的类中传递字段:

XXXReactHomeViewController *reactVC = [[XXXReactHomeViewController alloc]init];
reactVC.rnPath = @"SugarStack";
[self.navigationController pushViewController:reactVC animated:YES];

在RN端接收属性并跳转页面。使用 react-navigation 导航栏控制器,通过从原生接收的参数 path 判断要显示的屏幕:

import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

定义栈并放入导航中:

const SugarStack = createStackNavigator({
  SugarFriend,
  SugarFriendDetail,
  RosterSearch,
});
const App = function (props) {
  const AppNavigator = createSwitchNavigator(
    {
      AppStack,
      SugarStack,
    },
    {
      initialRouteName: props.path || 'AppStack',
    },
  );
  const Navigation = createAppContainer(AppNavigator);
  return (
    <Provider store={store}>
      <StatusBar translucent backgroundColor="#00000000" barStyle="dark-content" showHideTransition="Slide" />
      <Navigation />
    </Provider>
  );
};

5. H5页面调用原生页面进而调用RN页面

这需要使用深度链接(deep linking)技术。首先在RN配置导航容器,使其能够从传入的URI中提取路径:

const SimpleApp = createAppContainer(createStackNavigator({...}));
const prefix = 'mychat://';
const MainApp = () => <SimpleApp uriPrefix={prefix} />;

在AppDelegate文件中配置iOS应用程序处理该URI方案:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:app openURL:url options:options];
}

在Xcode中设置 info -> URL Type 为mychat,以启用深度链接。

二、打包

  1. 导出js bundle包和图片资源:在终端进入RN项目的根目录,创建文件夹(如 release_ios),运行命令:
react-native bundle --entry-file index.js --platform ios --dev false --bundle-output release_ios/main.jsbundle --assets-dest release_ios/
  1. 将资源包导入到iOS项目:将生成的 assetsmain.jsbundle 文件拖入iOS工程,勾选适当的选项。

  2. 打包发布:通过Xcode的 Product -> Archive 打ipa包。

三、调试中遇见的一点小问题

在iOS真机调试时,可能遇到连接超时或无法加载js server的问题。确保mac和手机连接同一网络后,检查xCode中是否有 域名.xip.io 文件。如果没有,可能是因为项目从别处拷贝,导致ip.txt文件缺失。解决方法是在 guessPackagerHost 方法中直接返回本机地址,而不是localhost。

另外,在RN环境6.0以上和React-navigation 4.x中,可能出现 null is not an object(evaluating '_RNGestureHandlerModule.default.Direction') 错误。确保Podfile文件中包含以下库:

pod 'RNGestureHandler', :podspec => '../node_modules/react-native-gesture-handler/RNGestureHandler.podspec'

通过以上步骤,可以有效地实现原生与React Native之间的通信,并解决常见的打包和调试问题。

0 Answers