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 (
<Image source={{uri: imgURI}} />
);
},
render() {
return (
<View>
{this.props.images.map(this.renderImage)}
</View>
);
}
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处理那个动作的处理结果。
// 遵循<RCTBridgeModule>代理,
@interface JCMoreViewController : JCBaseViewController<RCTBridgeModule>
// 在.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成功打印
由于项目主要框架是以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
果然,打印了
self.navigationController
,self.title
以及其他已赋值的属性,都是空。
在上一级的
JCMoreViewController
的Native页面的ViewDidLoad
里打印了self
对象,发现和ReactNative调用时的对象内存地址不一致,由此可见RCT_EXPORT_MODULE
暴露modlue
给 js
之后,会重新创建这个对象,导致其属性都是nil;
目前找的的解决方案是利用单例创建对象保证创建对象 的唯一性,个人比较喜欢用程序自带的AppDelegate
这个系统单例,因为视图的结构是 4个 NavigationController
放在 TabBarController
上,这四个NavigationController可以只创建一次,在AppDelegate
里初始化,然后添加到TabBarController
上。