ReactNative
去年Facebook发布了ReactNative之后, 各大厂也都反应积极, 今天就来抛抛砖。
ReactNative介绍在这里就不赘述了, 这里是官方介绍, 民间版在这里.
大家一般肯定有是有现成项目的, 完全新开一个项目可能性比较小, 所以集成就是重中之重了。
目标
在已有项目中我们使用ReactNative
实现一个功能模块, 使其可以与Native
代码进行通讯, 使其可以从ReactNative
跳到Native
中
实践
Facebook官方教程里面有关于如何集成的教程, 安卓版, iOS版
我们主要描述一下iOS版的过程, 必要条件请移步这里
大概熟悉一下之后, 进行如下步骤
首先建一个文件夹, 我们就叫
ReactNativeDemo
, 之后1
2
3
4cd ReactNativeDemo
npm init # 会问些问题什么的, 基本上测试一路回车就行了, 第一步的name不能有大些字母
npm install --save react-native
curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig都完成之后, 向
package.json
里的scripts
里面添加"start": "node_modules/react-native/packager/packager.sh"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 完成之后 package.json 看起来大概是这个样子
{
"name": "react-native-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node_modules/react-native/packager/packager.sh"
},
"author": "",
"license": "ISC",
"dependencies": {
"react-native": "^0.21.0"
}
}再添加一个文件叫
index.ios.js
, 内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37;
import React, {
TouchableHighlight,
Text,
View
} from 'react-native';
var styles = React.StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'orange'
}
});
class SimpleApp extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<Text>This is a simple application.</Text>
<TouchableHighlight onPress={ this._onPressButton.bind(this) }>
<Text>Tap me!</Text>
</TouchableHighlight>
</View>
)
}
_onPressButton(event) {
console.log("Press action");
}
}
React.AppRegistry.registerComponent('SimpleApp', () => SimpleApp);到这里
javascript
部分就差不多了, 我们开始建iOS项目新建一个工程叫
ReactNativeTest
, 放到我们刚建的文件夹ReactNativeDemo
里面, 因为iOS 9增强了安全性, 不允许HTTP
连接, 编辑工程的Info.plist
, 追加如下内容1
2
3
4
5
6
7
8
9
10
11<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>在工程根目录里新建一个文件
Podfile
1
2
3
4
5
6
7
8
9
10# Depending on how your project is organized, your node_modules directory may be
# somewhere else; tell CocoaPods where you've installed react-native from npm
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTImage',
'RCTNetwork',
'RCTText',
'RCTWebSocket',
# Add any other subspecs you want to use in your project
]在终端中执行
1
pod install
完成之后在
storyboard
里面拖大概这么个东西结构大概是
UITabBarController
管着两个UIViewController
, 上面那个我们用来显示ReactNative
内容, 下面那个是Native
的内容, 并且都用UINavigationController
包起来方便页面跳转再建一个
RNNotificationManager
, 语言选Objective-C
, 留空就好, 现在建这么个文件是为了生成swift
与Objective-C
通讯的头文件之后Xcode会提示生成一个头文件, 在那个文件里添加
#import "RCTRootView.h"
, 因为刚才我建的项目是swift
版的, 所以需要加这么一句以便swift
调用, 接着新建一个
ReactView.swift
内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import UIKit
class ReactView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
setup()
}
private func setup() -> () {
let jsCodeLocation = NSURL(string: "http://localhost:8081/index.ios.bundle?platform=ios")
let rootView = RCTRootView(
bundleURL: jsCodeLocation
, moduleName: "SimpleApp"
, initialProperties: .None
, launchOptions: .None
)
rootView.frame = bounds
addSubview(rootView)
rootView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
}然后在
storyboard
上面那个ViewController
的View
的class
写成ReactView
新建一个文件
NativeViewController
内容如下, 并且在storyboard
中把下面的那个ViewController
的class
指定为NativeViewController
, 再拖一个UILabel
放进来, 连到NativeViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = .Bottom // 上方不要扩展, 否则就看不到最上面那行了
}
func notificationHandler(notification: NSNotification) -> () {
// 留着后面用
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? NativeViewController {
vc.messageText = sender?.description
}
}
}
// NativeViewController.swift
import UIKit
class NativeViewController: UIViewController {
var messageText: String!
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if messageText == .None {
messageText = "Native Message"
}
label.text = messageText
}
}在
ReactNativeDemo
文件夹里面执行1
npm start
之后, 在Xcode运行iOS程序, 是这么个样子的东西
点Tap me!
控制台应该输出Press Action
, 说明一切正常了
ReactNative与Native通讯
现在我们可以着手进行通讯了, ReactNative
集成Native
内容有两种类型, 分别是Native UI Component使用原生代码写的节目, 与Native Module使用原生代码写的非界面模块。
这里, 我希望通过点击Tap me!
通过NavigationController push
跳进Native
的界面, 具体我想通过NSNotificationCenter
来实现。我们在点击的时候, 给通知中心发消息, 然后让ViewController
捕获消息进行页面跳转。
于是在刚才的RNNotificationManager
两个文件写成这样
1 | // RNNotificationManager.h |
修改index.ios.js
1 | // import 部分改为 |
最后, 修改ViewController.swift
1 | // viewDidLoad 追加 |
为什么notificationHandler
中要用GCD切换到主队列中执行performSegueWithIdentifier
呢?
这是因为ReactNative
很多操作都是异步执行的, 如果不切到主队列的话我们的界面是没有响应的, 因为“非主线程不能操作UI”嘛。
至此, 就大功告成了!
结尾
通过这个可行性实验, 我们可以结合具体的业务需求来具体实现。
假如我们已有的模块都可以拿出来使用的话, 设想我们所有的节目都有自己独立的identifier, 那么通过修改ReactNative
发送的消息就可以随便进入希望进入的页面, 从而达成“即使不是纯ReactNative项目, 我们也可以在线更新”这个愿望。