React和Native交互(附和JSPatch的区别)

相信使用过UIWebView的人应该都不陌生通过Js注入的方式,可以让JavaScript调用Native, React Native则是通过bridge的方式让Js端和Native端可以互相通信。

1 React调用Native

用简单的话来说, 为什么React能调用Native,和JSPatch的原理很像, JavaScriptCore是iOS系统内置的JS引擎,可以通过JavaScriptCore让OC给JS传数据,而JS调用Native实际是通过一张模块配置表,这个表在记录了OC上有哪些类的方法可以调用, 在通过bridge把这个表交给JS, 这样JS就可以调用OC了, 那JSPatch有什么不同呢?鄙人认为,JSPatch建立的bridge基于runtime的, 也就是一组规则,而React Native建立的bridge是基于表的,但是对JS端的语法要求很宽泛,你甚至可以在JS端去做网络请求,做逻辑,而JSPatch现在还是主要通过JS辅助Native去干些事情(可以是好事情,比如修BUG、崩溃, 也可以是坏事情,比如隐藏功能骗审核),但主体还是Native。现在还没见过一款完全以JSPatch的JS端为主体的APP, 有人发现了可以告诉我。但React Native不同, 虽然JS调用Native需要通过bridge, 但如果以React为主体, 你甚至根本不用去考虑JS调OC, 因为能在OC完成的事情都可以在JS里完成。像携程已经有一些APP完全采用了这套框架,Native端只有极少量的入口,全部工作都在React端。
下面给出React调Native的方式
OC端代码

//
//  ViewController.m
//  TestReactNative
//
//  Created by yy on 16/8/17.
//  Copyright © 2016年 BP. All rights reserved.
//

#import "ViewController.h"
#import "RCTRootView.h"

@interface ViewController ()

@property (nonatomic, weak) RCTRootView *rootView;

@end

@implementation ViewController
RCT_EXPORT_MODULE();
- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *jsCodeLocation;
    jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName:@"TestReactNative"
                                                 initialProperties:nil
                                                     launchOptions:nil];
    [self.view addSubview:rootView];
    self.rootView = rootView;
}

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

- (void)viewDidLayoutSubviews {
    self.rootView.frame = CGRectMake(0, 0, self.view.frame.size.width, 300);
    [super viewDidLayoutSubviews];
}

@end

通过

RCT_EXPORT_MODULE
RCT_EXPORT_METHOD
这两个宏,就告诉了JS,我ViewController里有个addEvent:(NSString *)name location:(NSString *)location方法啊, 欢迎来调用啊!

在React端, 可以用这样的方式调用

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  NativeModules,
  View
} from 'react-native';

class TestReactNative extends Component {
  componentDidMount() {
    var ViewController = NativeModules.ViewController;
    ViewController.addEvent('Birthday Party', '4 Privet Drive, Surrey');
  }

  render() {
    return (
      

      
      );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop:40,
  },
  text:{
    color:'red'
  },
});

AppRegistry.registerComponent('TestReactNative', () => TestReactNative);

这里componentDidMount是JS生命周期的一个方法,可以认为是JS里的viewDidLoad,通过拿到NativeModules相当于拿到了之前说的那个表NativeModules.ViewController相当于把表中ViewController模块中的方法列表的指针拿到了,然后就可以调用Native方法了。

1+ React调用Native,并通过block带回数据

如果刚才是一个同步的调用,JS告诉Native去处理事情,至于处理完怎么办,我不关心,可以使用1中的方法,但如果是异步的调用,Native处理完数据,还要把数据或者结果回传给JS,应当如何处理呢?React Native有几个RCTResponseXXXrBlock专门处理这种需求

typedef void (^RCTResponseSenderBlock)(NSArray *response);
typedef void (^RCTResponseErrorBlock)(NSError *error);
typedef void (^RCTPromiseResolveBlock)(id result);
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);

OC代码

//
//  ViewController.m
//  TestReactNative
//
//  Created by yy on 16/8/17.
//  Copyright © 2016年 BP. All rights reserved.
//

#import "ViewController.h"
#import "RCTRootView.h"

@interface ViewController ()

@property (nonatomic, weak) RCTRootView *rootView;

@end

@implementation ViewController
RCT_EXPORT_MODULE();
- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *jsCodeLocation;
    jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName:@"TestReactNative"
                                                 initialProperties:nil
                                                     launchOptions:nil];
    [self.view addSubview:rootView];
    self.rootView = rootView;
}

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

RCT_EXPORT_METHOD(findEvents1:(RCTResponseSenderBlock)callback)
{
    NSArray *events = @[@"12", @"13", @"23"];
    callback(@[[NSNull null], events]);
}

RCT_REMAP_METHOD(testRespondMethod,
                 name:(NSString *)name
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
    if([self respondsToSelector:NSSelectorFromString(name)]) {
        resolve(@YES);
    }
    else {
        reject(@"1001", @"not respond this method", nil);
    }
}

- (void)viewDidLayoutSubviews {
    self.rootView.frame = CGRectMake(0, 0, self.view.frame.size.width, 300);
    [super viewDidLayoutSubviews];
}

@end

JS端

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  NativeModules,
  View
} from 'react-native';

class TestReactNative extends Component {
  componentDidMount() {
    var ViewController = NativeModules.ViewController;
    ViewController.addEvent('Birthday Party', '4 Privet Drive, Surrey');
    ViewController.findEvents1((error, events) => {
      if (error) {
        console.error(error);
      } else {
        console.log(events);
      }
    });

    ViewController.testRespondMethod("fun")
      .then(result => {
      console.log("result is ", result);
      })
      .catch(error => {
      console.log(error);
    });
  }

  render() {
    return (
      

      
      );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop:40,
  },
  text:{
    color:'red'
  },
});

AppRegistry.registerComponent('TestReactNative', () => TestReactNative);

控制台输出结果

TestReactNative[2747:184048] Pretending to create an event Birthday Party at 4 Privet Drive, Surrey
2016-08-18 15:52:34.466 [info][tid:com.facebook.react.JavaScript] [ '12', '13', '23' ]
2016-08-18 15:52:34.467 [info][tid:com.facebook.react.JavaScript] 'result is ', true

2 Native通知JS

1和1+已经说明了如何JS如何同步异步调用Native的方法,但是应该还不够,还需要有Native通知JS的机制, why?比如对于使用protocolbuf协议的同学会知道, 不是所有服务端的回调都源于客户端的请求, 也可能是单向广播或者通知, 对于这种情况, 通知机制是最好的解决方案,没有通知,要如何解决服务端单向通知呢?
可以用如下的方案:JS端去调用Native的接口, 并通过callback带回数据,将这个callback作为Native的成员,当服务端通知回调时候,callback走起,带回数据,这种callback的解决方案,还可以使用sendAppEventWithName这个bridge eventDispatcher方法,要感谢facebook的大大们给我们提供了这种方便易懂的途径。
OC端代码

//
//  ViewController.m
//  TestReactNative
//
//  Created by yy on 16/8/17.
//  Copyright © 2016年 BP. All rights reserved.
//

#import "ViewController.h"
#import "RCTRootView.h"
#import "RCTEventDispatcher.h"
#import "RCTEventEmitter.h"

@interface ViewController ()

@property (nonatomic, weak) RCTRootView *rootView;

@end

@implementation ViewController
RCT_EXPORT_MODULE();
- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *jsCodeLocation;
    jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                        moduleName:@"TestReactNative"
                                                 initialProperties:nil
                                                     launchOptions:nil];
    [self.view addSubview:rootView];
    self.rootView = rootView;
}

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

RCT_EXPORT_METHOD(findEvents1:(RCTResponseSenderBlock)callback)
{
    NSArray *events = @[@"12", @"13", @"23"];
    callback(@[[NSNull null], events]);
}

RCT_REMAP_METHOD(testRespondMethod,
                 name:(NSString *)name
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
    if([self respondsToSelector:NSSelectorFromString(name)]) {
        resolve(@YES);
    }
    else {
        reject(@"1001", @"not respond this method", nil);
    }
}

- (void)viewDidLayoutSubviews {
    self.rootView.frame = CGRectMake(0, 0, self.view.frame.size.width, 300);
    [super viewDidLayoutSubviews];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSString *eventName = @"touchesBegan";
    [self.rootView.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
                                                          body:@{@"name": eventName}];
}
@end

JS端代码

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  NativeModules,
  AlertIOS,
  View
} from 'react-native';

class TestReactNative extends Component {
  componentDidMount() {
    var ViewController = NativeModules.ViewController;
    ViewController.addEvent('Birthday Party', '4 Privet Drive, Surrey');
    ViewController.findEvents1((error, events) => {
      if (error) {
        console.error(error);
      } else {
        console.log(events);
      }
    });

    ViewController.testRespondMethod("dealloc")
      .then(result => {
      console.log("result is ", result);
      })
      .catch(error => {
      console.log(error);
    });

    var { NativeAppEventEmitter } = require('react-native');

    var subscription = NativeAppEventEmitter.addListener(
      'EventReminder',
      (reminder) => { 
       console.log(reminder.name);
       AlertIOS.alert(
      'Foo Title',
      'My Alert Msg',
      [
        {
          text: 'Foo', onPress: function FooClick() {
          console.log('Foo Pressed!');}
        },
        {
          text: 'Bar', onPress: () => console.log('Bar Pressed!')
        },
      ]);}
     );
  }

  render() {
    return (
      

      
      );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop:40,
  },
  text:{
    color:'red'
  },
});

AppRegistry.registerComponent('TestReactNative', () => TestReactNative);

NativeAppEventEmitter注册了name是EventReminder的listener类似OC的

(void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;

这里reminder作为OC带回来的数据,也就是body里的字典。

走马观花洋洋洒洒的写了3篇对React Native的粗浅的认识, 旨在帮助那些iOS开发者,希望他们能够快速的对React Native有个感官的认识, 希望用最朴素的语言说明白,什么是React Native,React Native能干啥, 用了React Native有什么好处。欢迎大家积极交流, 能力有限,难免有写的不准确的地方, 还望大神多多指点。

你可能感兴趣的:(React和Native交互(附和JSPatch的区别))