相信使用过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有什么好处。欢迎大家积极交流, 能力有限,难免有写的不准确的地方, 还望大神多多指点。