核心概念#
React Native is great when you are starting a new mobile app from scratch. However, it also works well for adding a single view or user flow to existing native applications. With a few steps, you can add new React Native based features, screens, views, etc.
用React Native去开发一个全新的手机App是个很好的选择,并且React Native也可以和原生App整合。用几个步骤,你就可以将React Native的基础特性,视图,窗体等控件加入到现有原生App中。
在iOS App中整合RN组件的关键点如下:
一 理解需要整合的React Native组件。
二 为所有需要整合的React Native组件创建带有subspecs的Podfile。
三 在Javascript中创建你的React Native组件。
四 增加一个新的事件句柄,该句柄用于创建一个RCTRootView,该RCTRootView用于指向你的React Native组件,事件句柄的AppRegistry名称需要在index.ios.js中定义。
五 启动React Native server,并运行原声应用。
六 选择性的增加更多的React Native组件。
七 调试
八 准备发布(比如:运行脚本 react-native-xcode.sh)
九 正式发布
先决条件
总述
首先,按照Getting Started guide把开发环境搞定,先让React Native在iOS上能跑起来。
CocoaPods
CocoaPods是一个打包管理工具,支持MAC和iOS的开发。我们使用它用于在本地增加一个React Native framework的代码到我们现有的工程中。
$ sudo gem install cocoapods
从技术上讲,不使用CocoaPods是可能的,但是这需要手动链接新增的静态库。
示例App
假设需要整合的APP是2048这个游戏。下面是一个 不包含React Native的原生应用的主菜单。
包依赖#
React Native 整合需要React和React Native节点模块。React Native Framework将提供代码以便于你的应用整合可以执行。
package.json #
我们可以增加依赖package.json文件的包。如果这个包在你的工程的根目录不存在,则创建该文件。
通常在React Navie工程的根目录中,你将放入一些像package.json,index.ios.js这样的文件,并且在形如 ios/的子目录中增加仅在iOS上运行的原生代码,这些代码在本地运行。
下面是一个最简单的的package.json的例子。
版本号按照你的需要写入,通常用最新的React和React Native版本号。
{
"name": "NumberTileGame",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.0.2",
"react-native": "0.26.1"
}
}
包安装#
通过Node package manager安装React和React Native模块。Node模块需要被装到你的工程的根目录的node_modules/子路径中。
#在包含package.json的工程中,安装模块至node_modules/文件夹
$ npm install
React Native Framework #
按照上面的命令安装React Native Framework到你的工程中。我们将安装CocoaPods Podfile,其中携带你希望使用的组件。
Subspecs #
在你整合React Native到你的应用之前,你需要决定那部分React Native Framework你希望去整合。这就是需要引入subspecs的地方。当你创建你的Podfile的时候,你需要指定使用并依赖哪些React Native库,每个库将变成在Podfile中的一个subspec。
需要支持的subspecs列表可在node_modules/react-native/React.podspec中查看。他们大都按照功能命名。比如,我们一直要使用的Core subspec。它可以提供AppRegistry,StyleSheet,View和其他核心React Native库。如果你要增加React Native的Text库,即支持<Text>元素,这时你需要RCTText subspec。如果你需要Image库,即支持<Image>元素,这时你需要RCTImage subspec。
Podfile #
在你使用Node去安装React和 React Native frameworks至node_modules路径之后,并且你已经决定你需要整合哪些React Native元素,这时你需要准备去创建Podfile了,以便于你可以安装这些组件到你的应用中。
最简单的方法去创建一个Podfile是通过是在原生iOS代码路径中用CocoaPods的 init命令创建。
## In the directory where your native iOS code is located (e.g., where your `.xcodeproj` file is located)
$ pod init
Podfile将被创建和保存在iOS的当前工程路径(ios/)中,并且将包含一个样本文件设定,用于微调你的整合目的。最后,Podfile应该看起来像下面这样:
# The target name is most likely the name of your project.
target 'NumberTileGame' do
# Your 'node_modules' directory is probably in the root of your project,
# but if not, adjust the `:path` accordingly
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]
end
Pod 安装#
在你创建完毕Podfile后,你就可以准备安装React Native pod了。
$ pod install
你应该看到输出如下:
Analyzing dependencies
Fetching podspec for `React` from `../node_modules/react-native`
Downloading dependencies
Installing React (0.26.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 3 dependencies from the Podfile and 1 total pod installed.
Code 整合#
现在我们有一个基础包了,我们将实现修改原生应用,将React Native整合到应用中。对于我们的2048应用,我们将在React Native中增加一个"High Score”屏幕。
React Native 组件#
我们将写的第一段代码是用React Native写的新的"High Score”屏幕,这将被整合入你的应用。
创建index.ios.js文件#
首先,创建一个空的index.ios.js文件。为了方便,我们将该文件置于工程根目录。index.ios.js是iOS的React Native应用的起点,并且该文件是必须的。该文件可以是一个小文件,用于表示你的React Native组件或者应用需要哪些其它的文件,或者它可以包含所需的所有代码。
# In root of your project
$ touch index.ios.js
增加React Native代码#
在你的ndex.ios.js文件中创建你的组件,这里的例子中,我们在<View>中添加<Text>组件。
'use strict';
import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class RNHighScores extends React.Component {
render() {
var contents = this.props["scores"].map(
score => <Text key={score.name}>{score.name}:{score.value}{"\n"}</Text>
);
return (
<View style={styles.container}>
<Text style={styles.highScoresTitle}>
2048 High Scores!
</Text>
<Text style={styles.scores}>
{contents}
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
},
highScoresTitle: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
scores: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
// Module name
AppRegistry.registerComponent('RNHighScores', () => RNHighScores);
RNHighScores 是你使用React Native增加的一个视图模块名称。
关于RCTRootView#
现在在index.ios.js中创建你的React Native组件,你需要添加组件至一个新建的或者是已存在的ViewController中。最简单的办法是任意创建一个事件路径到你的组件中,然后将这个组件加到ViewController中。下一步,我们将React Native组件和一个在ViewController中的新建的原生视图绑定,该ViewController被命名为RCTRootView。
创建事件路径#
You can add a new link on the main game menu to go to the "High Score" React Native page.
你可以在主游戏菜单中增加一个新链接便于跳转到"High Score"这个React Native页面。
事件句柄#
我们将在菜单链接中增加一个事件句柄。在应用的ViewController中增加一个方法。这时,RCTRootView就开始起作用了。
当你建立一个React Native应用,你可以使用React Native打包器去创建index.ios.bundle,用于对接React Native server。因此,我们需要通过NSURL让RCTRootView指向index.ios.bundle,并且将它和该模块绑定。
处于便于调试的目的,我们要在时间句柄被唤醒时打log。然后,在本地的React Native代码中创建一个字符串,这些代码在index.ios.bundle文件中。
最后,创建主窗体RCTRootView。在写React Native组件代码的时候,注意我们是如何提供RNHighScores作为模块名的。
#import "RCTRootView.h"
initialProperties是保存一些初始化数据的,后续可以在代码中用this.props获取这些数据。
- (IBAction)highScoreButtonPressed:(id)sender {
NSLog(@"High Score Button Pressed");
NSURL *jsCodeLocation = [NSURL
URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBundleURL : jsCodeLocation
moduleName : @"RNHighScores"
initialProperties :
@{
@"scores" : @[
@{
@"name" : @"Alex",
@"value": @"42"
},
@{
@"name" : @"Joel",
@"value": @"10"
}
]
}
launchOptions : nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
}
注意RCTRootView的initWithURL会启动一个新的JSC(JavaScriptCore)虚拟机。保存资源和简化不同的RN窗体间的通信,你可以有很多RN窗体,这些窗体与单一JS执行时间相关。为此,可以用RCTBridge initWithBundleURL 创建一个桥,用于代替RCTRootView的initWithURL。
当你的应用要发布的时候,NSURL可以指向一个预打包文件,类似这样:
[[NSBundle mainBundle] URLForResource:@"main"
withExtension:@"jsbundle"];.
你可以在node_modules/react-native/packager/路径中使用react-native-xcode.sh脚本去生产一个预打包文件。
连接#
连接主菜单中的新链接和新增加的事件句柄方法。
一个较为简便的方法是打开storyboard,右击新链接。选择类似Touch Up Inside事件,拖动到storyboard中,然后选择在列表中创建方法。
测试你的整合应用#
你已经完成了所有步骤去整合RN和你的APP。现在我们开始用RN打包器去建立index.ios.bundle包,便于localhost的服务器去加载。
App的通信安全#
Apple has blocked implicit cleartext HTTP resource loading. So we need to add the following our project's Info.plist (or equivalent) file.
Apple公司已经关闭了隐式明文HTTP资源加载的接口。因此,我们需要在Info.plist文件中增加下面这段内容。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
运行 Packager#
# From the root of your project, where the `node_modules` directory is located.
$ npm start
运行 App#
如果你使用Xcode或者其它代码编辑器,你可以像以往一样建立和运行你的原生iOS APP。或者,你可以用下面这种命令行来运行APP:
# From the root of your project
$ react-native run-ios
在我们的示例应用中,你可以看到"High Scores”的链接,然后当你点击链接的时候,RN组件将会被渲染。
原生应用的 home 视图:
React Native 的high score视图:
如果你在整合的时候遇到获取模块的问题,可以参考下面的链接:
https://github.com/facebook/react-native/issues/4968
源代码
你可以参考一下整合相关的源代码GitHub.
原文链接:
http://facebook.github.io/react-native/releases/0.33/docs/integration-with-existing-apps.html#subspecs