ReactNative 与 Native 通信机制实践

ReactNative 2015年在FaceBook的React.js conf开源后,迅速的发展,迭代了许多版本,现在比较稳定了,前段时间首次在项目中首次使用了ReactNative 与Native 混编,在这里把自己遇到的问题以及解决方法跟大家分享一下,欢迎补充和指正。

由于Apple的强势,完全使用ReactNative 风险较高,比如AppleStore 审核时检测到bundle.js文件不让上架。出于稳健考虑,使用ReactNative 与Native混编。

总的来说RN混编包括几种方式:
1.总框架用RN,部分页面通过自定义原生组件,在ReactJs里调用。
2.总框架使用Native编写,部分灵活性较高的页面以及活动页面使用ReactNative。
3.还有在RN页面里嵌套Native页面以及在Native视图上添加RN子视图

我之前使用的Objective-C与Swift混编,个人偏向第二种方式。
具体的环境搭建,引入Native工程等在这里就不细说了。这里主要讲一下ReactNative与Native的通信

http://facebook.github.io/react-native/docs/getting-started.html

跨组件通信

属性 从Native传递属性到ReactNative
RCTRootView是一个UIView容器,承载着ReactNative页面,在其初始化方法里提供了一个接口用于传递参数。

    NSURL *jsCodeLocation;

  /**
   * Loading JavaScript code - uncomment the one you want.
   *
   * OPTION 1
   * Load from development server. Start the server from the repository root:
   *
   * $ npm start
   *
   * To run on device, change `localhost` to the IP address of your computer
   * (you can get this by typing `ifconfig` into the terminal and selecting the
   * `inet` value under `en0:`) and make sure your computer and iOS device are
   * on the same Wi-Fi network.
   */
  
    jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/connectUs.ios.bundle"];               

  
  /*
   *  属性传递参数
   */
   
    NSArray *imageList = @[@"http://foo.com/bar1.png",
                  @"http://foo.com/bar2.png"];

    NSDictionary *props = @{@"images" : imageList};
  
  
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                  moduleName:@"JCProject"
                                           initialProperties:props
                                               launchOptions:nil];

在JS文件中直接调用

renderImage: function(imgURI) {
return (
  
);
},
  render() {
    return (
      
        {this.props.images.map(this.renderImage)}
      
    );
}

RCTRootView 提供了一个读写权限的appProperties属性

NSArray *imageList = @[@"http://foo.com/bar3.png",
               @"http://foo.com/bar4.png"];
rootView.appProperties = @{@"images" : imageList};

可以随时更新属性,但更新必须在主线程中进行,读取可在任何线程中
属性的限制
跨语言属性的主要缺点是不支持回调方法,因而无法实现自下而上的数据绑定。设想你有一个小的RN视图,当一个JS动作触发时你想从原生的父视图中移除它。此时你会发现根本做不到,因为信息需要自下而上进行传递。

ReactNative 调用Native
对于ReactNative调Native 官方是这样说的

虽然我们有跨语言回调,但是这些回调函数并不总能满足需求。最主要的问题是它们并不是被设计来当作属性进行传递。这一机制的本意是允许我们从JS触发一个原生动作,然后用JS处理那个动作的处理结果。

//  遵循代理,
@interface JCMoreViewController : JCBaseViewController


//  在.m文件里实现 RCT_EXPORT_MODULE()方法,()中可以添加一个参数,指定在JS里访问这个模块的名字,如果不指定则默认使用类名
RCT_EXPORT_MODULE(jcModule);

// 声明通过通过 RCT_EXPORT_METHOD()宏来实现申明给Javascript导出的方法:
RCT_EXPORT_METHOD(setEvent:(NSString *)event location:(NSString *)location) {

 NSLog(@"Pretending to create an event %@ at %@", event, location);

}

在JS中你可以这用调用

  loadNative:function() {
    
     var JCMoreViewController = require('react-native').NativeModules.JCMoreViewController;
     
     JCMoreViewController.setEvent('ReactToNative', 'Success');
 }

此处有坑
通过这种方式,确实可以实现ReactJS调用Native方法,Log成功打印

RN调用Native成功

由于项目主要框架是以Objective-C与Swift原生写的,在ReactNative与Natvie 跳转中统一使用了Native的NavigationBar,由ViewController 作为视图容器,通过上面RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD()实现在ReactNative视图中,调用原生方法

RCT_EXPORT_METHOD(setEvent:(NSString *)event location:(NSString *)location)
{
    NSLog(@"Pretending to create an event %@ at %@", event, location);

    [self.navigationController popToRootViewControllerAnimated:YES];
}

通过Module写了一个跳转方法,然而并没有执行跳转,但是却打印了Log。

第一反应是不在主线程,在代码执跳转前面添加了

[[NSThread currentThread] setName:@"com.shen.stark"];
NSLog(@"mainThread =%d currentThread = %@",[NSThread isMainThread],[NSThread currentThread]);
当前线程
[self.navigationController performSelectorOnMainThread:@selector(popToRootViewControllerAnimated:) withObject:@YES waitUntilDone:YES];

在主线程里执行依然没有反应,

RCT_EXPORT_METHOD(setEvent:(NSString *)event location:(NSString *)location) {
NSLog(@"Pretending to create an event %@ at %@", event, location);
NSLog(@"mainThread =%d currentThread = %@",[NSThread isMainThread],[NSThread currentThread]);

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"mainThread =%d currentThread = %@",[NSThread isMainThread],[NSThread currentThread]);
//isMainThread = 1    在主线程

[self.navigationController popToRootViewControllerAnimated:YES];

NSLog(@"ping");    //正常打印
});
}
线程信息

主线程中跳转后面的Log都打印了,排除了线程问题,有可能是视图层次问题?获取不到self.navigationController

对象属性为null

果然,打印了 self.navigationControllerself.title以及其他已赋值的属性,都是空。

ViewDidLoad中打印的对象的内存地址

在上一级的 JCMoreViewController的Native页面的 ViewDidLoad里打印了 self对象,发现和ReactNative调用时的对象内存地址不一致,由此可见 RCT_EXPORT_MODULE暴露 modluejs之后,会重新创建这个对象,导致其属性都是nil;

目前找的的解决方案是利用单例创建对象保证创建对象 的唯一性,个人比较喜欢用程序自带的AppDelegate这个系统单例,因为视图的结构是 4个 NavigationController放在 TabBarController上,这四个NavigationController可以只创建一次,在AppDelegate里初始化,然后添加到TabBarController上。

你可能感兴趣的:(ReactNative 与 Native 通信机制实践)