React native

Integration with Existing Apps

Integration with Existing Apps

需要本机代码的项目

此页面仅适用于react-native init使用Create React Native App 制作或使用此类应用程序弹出的项目。有关弹出的更多信息,请参阅创建React Native App存储库的指南。

当您从头开始创建新的移动应用程序时,React Native非常棒。但是,它也适用于将单个视图或用户流添加到现有的本机应用程序。通过几个步骤,您可以添加新的基于React Native的功能,屏幕,视图等。

具体步骤根据您定位的平台而有所不同。

  • iOS (Objective-C)

  • iOS (Swift)

  • Android (Java)

关键概念

将React Native组件集成到您的iOS应用程序中的关键是:

  • 设置React Native依赖关系和目录结构。

  • 了解您将在您的应用中使用哪些React Native组件。

  • 使用CocoaPods将这些组件添加为依赖项。

  • 在JavaScript中开发您的React Native组件。

  • 添加RCTRootView到你的iOS应用程序。该视图将用作React Native组件的容器。

  • 启动React Native服务器并运行您的本机应用程序。

  • 验证应用程序的React Native方面是否按预期工作。

将React Native组件集成到您的Android应用程序中的关键是:

  • 设置React Native依赖关系和目录结构。

  • 在JavaScript中开发您的React Native组件。

  • 添加ReactRootView到您的Android应用程序。该视图将用作React Native组件的容器。

  • 启动React Native服务器并运行您的本机应用程序。

  • 验证应用程序的React Native方面是否按预期工作。

先决条件

请按照使用入门指南中使用本机代码构建应用程序的说明来配置开发环境,以构建适用于iOS的React Native应用程序。

1.设置目录结构

为确保顺利体验,请为您的集成React Native项目创建一个新文件夹,然后将现有iOS项目复制到/ios子文件夹。

请按照使用入门指南中的使用本机代码构建应用的说明来配置开发环境,以构建适用于Android的React Native应用。

1.设置目录结构

为确保顺利体验,请为您的集成React Native项目创建一个新文件夹,然后将现有Android项目复制到/android子文件夹。

2.安装JavaScript依赖关系

转到项目的根目录并package.json使用以下内容创建一个新文件:

{ "name": "MyReactNativeApp", "version": "0.0.1", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start" } }

接下来,您将安装reactreact-native包。打开终端或命令提示符,然后导航到项目的根目录并输入以下命令:

$ npm install --save react@16.0.0-beta.5 react-native

确保您使用React Native package.json文件中指定的相同React版本。只要React Native依赖于React的预发布版本,这将是必要的。

这将/node_modules在您的项目根目录中创建一个新文件夹。该文件夹存储构建项目所需的所有JavaScript依赖项。

3.安装CocoaPods

CocoaPods是iOS和MacOS开发包管理工具。我们用它来将实际的React Native框架代码本地添加到当前项目中。

我们建议使用Homebrew安装CocoaPods 。

$ brew install cocoapods

技术上可以不使用CocoaPods,但是这需要手动添加库和链接器,这会使这个过程复杂化。

将React Native添加到您的应用程序

假设整合应用程序2048年的游戏。以下是本机应用程序的主菜单,没有React Native。

假设整合应用程序2048年的游戏。以下是本机应用程序的主菜单,没有React Native。

配置CocoaPods依赖

在将React Native集成到应用程序之前,您需要确定您想要集成React Native框架的哪些部分。我们将使用CocoaPods指定您的应用依赖哪些“子类”。

受支持subspec的列表可在/node_modules/react-native/React.podspec。它们通常以功能命名。例如,你通常会一直想要Core subspec。这将让你在AppRegistry,StyleSheet,View和其他芯反应,本机库。如果你想添加React Native Text库(例如,对于<Text>元素),那么你将需要RCTText subspec。如果你想要Image图书馆(例如,为<Image>元素),那么你将需要的RCTImage subspec。

您可以指定subspec您的应用将依赖于Podfile文件中的哪一个。最简单的创建方法Podfileinit/ios项目的子文件夹中运行CocoaPods 命令:

$ pod init

Podfile将包含一个样板的设置,你将调整为你整合的目的。最后,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', 'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43 'RCTText', 'RCTNetwork', 'RCTWebSocket', # needed for debugging # Add any other subspecs you want to use in your project ] # Explicitly include Yoga if you are using RN >= 0.42.0 pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' end

source 'https://github.com/CocoaPods/Specs.git' # Required for Swift apps platform :ios, '8.0' use_frameworks! # The target name is most likely the name of your project. target 'swift-2048' 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', 'CxxBridge', # Include this for RN >= 0.47 'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43 'RCTText', 'RCTNetwork', 'RCTWebSocket', # needed for debugging # Add any other subspecs you want to use in your project ] # Explicitly include Yoga if you are using RN >= 0.42.0 pod "yoga", :path => "../node_modules/react-native/ReactCommon/yoga" end

创建完成后Podfile,即可安装React Native窗格。

$ 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.

如果你得到一个警告,如“ 目标将覆盖在设置中定义构建__,这可能导致与安装的CocoaPods问题 ”,然后确保在两个和仅包含。swift-2048 [Debug] FRAMEWORK_SEARCH_PATHS Pods/Target Support Files/Pods-swift-2048/Pods-swift-2048.debug.xcconfigFramework Search PathsBuild SettingsDebugRelease$(inherited)

代码集成

现在我们将实际修改本地iOS应用程序以集成React Native。对于我们的2048示例应用程序,我们将在React Native中添加一个“高分”屏幕。

React Native组件

我们将编写的第一部分代码是新的“高分”屏幕的实际React Native代码,该代码将集成到我们的应用程序中。

1. Create a index.js file

首先,index.js在您的React Native项目的根目录下创建一个空文件。

index.js是React Native应用程序的起点,并且始终是必需的。它可以是一个小文件,require它是React Native组件或应用程序的一部分,也可以包含所需的所有代码。在我们的例子中,我们只是把所有东西都放进去index.js

2.添加您的React Native代码

在你的index.js,创建你的组件。在我们的示例中,我们将<Text>在样式中添加简单的组件<View>

'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('MyReactNativeApp', () => RNHighScores

RNHighScores 是从iOS应用程序向React Native添加视图时将使用的模块的名称。

The Magic: RCTRootView

既然您的React Native组件是通过创建的index.js,您需要将该组件添加到新的或现有的组件中ViewController。最简单的路径是可选地为您的组件创建事件路径,然后将该组件添加到现有组件ViewController

我们将把我们的React Native组件与一个新的本地视图绑定在一起,这个视图ViewController实际上将其称为主机RCTRootView

1.创建一个事件路径

您可以在主游戏菜单上添加一个新链接,进入“高分”React Native页面。

2.事件处理程序

我们现在将从菜单链接添加一个事件处理程序。方法将被添加到ViewController您的应用程序的主要部分。这是RCTRootView发挥作用的地方。

当您构建React Native应用程序时,您可以使用React Native包装程序创建一个index.bundle将由React Native服务器提供服务的应用程序。里面index.bundle将是我们的RNHighScore模块。所以,我们需要RCTRootViewindex.bundle资源的位置(通过NSURL)指向模块。

为了调试目的,我们将记录事件处理程序被调用。然后,我们将创建一个字符串,其中包含React Native代码的位置index.bundle。最后,我们将创建主要RCTRootView。注意,我们提供RNHighScoresmoduleName书面我们的代码时,我们在上面创建阵营机组件。

首先importRCTRootView标题。

#import <React/RCTRootView.h>

initialProperties在这里用于说明目的,所以我们有一些数据对我们的高分屏。在我们的React Native组件中,我们将使用它this.props来访问这些数据。

- (IBAction)highScoreButtonPressed:(id)sender { NSLog(@"High Score Button Pressed" NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.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虚拟机。为了节省资源并简化本机应用程序不同部分中的RN视图之间的通信,您可以让React Native支持多个视图,这些视图与单个JS运行时相关联。要做到这一点,而不是使用[RCTRootView alloc] initWithURL,使用RCTBridge initWithBundleURL创建一个桥梁,然后使用RCTRootView initWithBridge

首先importReact图书馆。

import React

initialProperties在这里用于说明目的,所以我们有一些数据对我们的高分屏。在我们的React Native组件中,我们将使用它this.props来访问这些数据。

@IBAction func highScoreButtonTapped(sender : UIButton) { NSLog("Hello") let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios") let mockData:NSDictionary = ["scores": [ ["name":"Alex", "value":"42"], ["name":"Joel", "value":"10"] ] ] let rootView = RCTRootView( bundleURL: jsCodeLocation, moduleName: "RNHighScores", initialProperties: mockData as [NSObject : AnyObject], launchOptions: nil ) let vc = UIViewController() vc.view = rootView self.present(vc, animated: true, completion: nil) }

请注意,RCTRootView bundleURL启动一个新的JSC虚拟机。为了节省资源并简化本机应用程序不同部分中的RN视图之间的通信,您可以让React Native支持多个视图,这些视图与单个JS运行时相关联。要做到这一点,而不是使用RCTRootView bundleURL,使用RCTBridge initWithBundleURL创建一个桥梁,然后使用RCTRootView initWithBridge。将应用程序移至生产环境时,NSURL可以通过类似方式指向磁盘上的预捆绑文件[[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];。您可以使用该react-native-xcode.sh脚本node_modules/react-native/scripts/来生成预先捆绑的文件。将应用程序移至生产环境时,NSURL可以通过类似方式指向磁盘上的预捆绑文件let mainBundle = NSBundle(URLForResource: "main" withExtension:"jsbundle")。您可以使用该react-native-xcode.sh脚本node_modules/react-native/scripts/来生成预先捆绑的文件。

3.接线

将主菜单中的新链接连接到新添加的事件处理程序方法。

更简单的方法之一是打开故事板中的视图并右键单击新链接。选择Touch Up Inside事件,将其拖到故事板,然后从提供的列表中选择创建的方法。

测试你的整合

您现在已经完成了将React Native与当前应用程序集成的所有基本步骤。现在我们将启动React Native打包程序来构建index.bundle软件包并运行服务器localhost来为其提供服务。

1.添加应用程序传输安全例外

Apple已经阻止隐式明文HTTP资源加载。所以我们需要添加以下我们的项目Info.plist(或等效)文件。

<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>localhost</key> <dict> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict>

App Transport Security对您的用户非常有用。确保在发布您的应用程序进行制作之前重新启用它。

2.运行打包程序

要运行您的应用程序,您需要先启动开发服务器。为此,只需在React Native项目的根目录中运行以下命令:

$ npm start

3.运行应用程序

如果您使用的是Xcode或您最喜欢的编辑器,请照常构建并运行您的本机iOS应用程序。或者,您可以使用以下命令从命令行运行应用程序:

# From the root of your project $ react-native run-ios

在我们的示例应用程序中,您应该看到指向“高分”的链接,然后点击该链接时,您将看到React Native组件的呈现。

以下是本应用程序主屏幕:

这里是React Native高分屏幕:

如果您在运行应用程序时遇到模块解析问题,请参阅此GitHub问题以获取信息和可能的解决方案。这个评论似乎是最新的可能的解决方案。

见守则

您可以检查在GitHub上将示例应用程序添加到React Native屏幕的代码。

您可以检查在GitHub上将示例应用程序添加到React Native屏幕的代码。

将React Native添加到您的应用程序

配置maven

将React Native依赖项添加到您的应用程序build.gradle文件中:

dependencies { ... compile "com.facebook.react:react-native:+" // From node_modules. }

如果您想确保始终在本机构建中使用特定的React Native版本,请+使用您从其下载的实际React Native版本进行替换npm

为本地React Native maven目录添加一个条目build.gradle。一定要将其添加到“allprojects”块中:

allprojects { repositories { ... maven { // All of React Native (JS, Android binaries) is installed from npm url "$rootDir/node_modules/react-native/android" } } ... }

确保路径正确!在Android Studio中运行Gradle同步后,您不应该遇到任何“无法解析:com.facebook.react:react-native:0.xx”错误。

配置权限

接下来,请确保您拥有Internet权限AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

如果您需要访问DevSettingsActivity添加到您的AndroidManifest.xml

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

这只在开发服务器重新加载JavaScript时才真正用于开发模式,因此如果需要,可以在发布版本中将其剥离。

代码集成

现在我们将实际修改本机Android应用程序以集成React Native。

React Native组件

我们将编写的第一部分代码是新的“高分”屏幕的实际React Native代码,该代码将集成到我们的应用程序中。

1.创建一个index.js文件

首先,index.js在您的React Native项目的根目录下创建一个空文件。

index.js是React Native应用程序的起点,并且始终是必需的。它可以是一个小文件,require它是React Native组件或应用程序的一部分,也可以包含所需的所有代码。在我们的例子中,我们只是把所有东西都放进去index.js

2.添加您的React Native代码

在你的index.js,创建你的组件。在我们的示例中,我们将<Text>在样式中添加简单的组件<View>:

'use strict'; import React from 'react'; import { AppRegistry, StyleSheet, Text, View } from 'react-native'; class HelloWorld extends React.Component { render() { return ( <View style={styles.container}> <Text style={styles.hello}>Hello, World</Text> </View> ) } } var styles = StyleSheet.create{ container: { flex: 1, justifyContent: 'center', }, hello: { fontSize: 20, textAlign: 'center', margin: 10, }, } AppRegistry.registerComponent('MyReactNativeApp', () => HelloWorld

3.配置开发错误覆盖的权限

如果您的应用定位到Android API level 23或更高版本,请确保您已overlay为开发版本启用权限。你可以检查它Settings.canDrawOverlays(this。这在开发版本中是必需的,因为必须在所有其他窗口之上显示原始开发错误。由于在API级别23中引入了新的权限系统,用户需要批准它。这可以通过将以下代码添加到onCreate()方法中的Activity文件中来实现。OVERLAY_PERMISSION_REQ_CODE是将负责将结果传递回活动的类的字段。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()) startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE } }

最后,onActivityResult()必须重写该方法(如下面的代码所示)以处理一致UX的权限Accepted或Denied。

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { // SYSTEM_ALERT_WINDOW permission not granted... } } } }

神奇的是: ReactRootView

您需要添加一些本机代码才能启动React Native运行时并让它呈现某些内容。为此,我们将创建一个Activity创建一个ReactRootView,在其中启动一个React应用程序并将其设置为主内容视图。

如果你的目标是Android版本<5,请使用包中的AppCompatActivity类com.android.support:appcompat而不是Activity。

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler { private ReactRootView mReactRootView; private ReactInstanceManager mReactInstanceManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState mReactRootView = new ReactRootView(this mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build( mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null setContentView(mReactRootView } @Override public void invokeDefaultOnBackPressed() { super.onBackPressed( } }

如果您正在使用React Native的入门工具包,请将您的index.js文件(它是该AppRegistry.registerComponent()方法的第一个参数)中的一个替换为“HelloWorld”字符串。

如果您正在使用Android Studio,请使用Alt + Enter在MyReactActivity类中添加所有缺少的导入。小心使用包装,BuildConfig而不是包装中的...facebook...包装。

我们需要的主题设置MyReactActivityTheme.AppCompat.Light.NoActionBar,因为一些组件依赖于这个主题。

<activity android:name=".MyReactActivity" android:label="@string/app_name" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> </activity>

A ReactInstanceManager可以在多个活动和/或片段中共享。你会想使自己ReactFragmentReactActivity并有单,其保持ReactInstanceManager。当你需要ReactInstanceManager(例如,连接ReactInstanceManager到这些活动或片段的生命周期)时,使用单例提供的那个。

接下来,我们需要将一些活动生命周期回调传递给ReactInstanceManager

@Override protected void onPause() { super.onPause( if (mReactInstanceManager != null) { mReactInstanceManager.onHostPause(this } } @Override protected void onResume() { super.onResume( if (mReactInstanceManager != null) { mReactInstanceManager.onHostResume(this, this } } @Override protected void onDestroy() { super.onDestroy( if (mReactInstanceManager != null) { mReactInstanceManager.onHostDestroy( } }

我们还需要将按钮事件传递给React Native:

@Override public void onBackPressed() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed( } else { super.onBackPressed( } }

这允许JavaScript控制用户按下硬件后退按钮时发生的情况(例如,实现导航)。当JavaScript不处理背按时,您的invokeDefaultOnBackPressed方法将被调用。默认情况下,这只是完成你的Activity

最后,我们需要连接开发菜单。默认情况下,这是通过(愤怒)激发设备来激活的,但这在模拟器中并不是很有用。所以我们在按下硬件菜单按钮时显示它(Ctrl + M如果您使用的是Android Studio模拟器,请使用它):

@Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog( return true; } return super.onKeyUp(keyCode, event }

现在您的活动已准备好运行一些JavaScript代码。

测试你的整合

您现在已经完成了将React Native与当前应用程序集成的所有基本步骤。现在我们将启动React Native打包程序来构建index.bundle包并运行在本地主机上的服务器来提供它。

1.运行打包程序

要运行您的应用程序,您需要先启动开发服务器。为此,只需在React Native项目的根目录中运行以下命令:

$ npm start

2.运行应用程序

现在像平常一样构建并运行Android应用程序。

一旦您在应用程序内部获得了React支持的活动,它应该从开发服务器加载JavaScript代码并显示:

在Android Studio中创建发布版本

您可以使用Android Studio创建发布版本!这与创建以前存在的原生Android应用程序的发布版本一样简单。还有一个额外的步骤,在每次发布之前您都必须完成。您需要执行以下操作来创建React Native软件包,该软件包将包含在您的原生Android应用程序中:

$ react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/

不要忘记用正确的路径替换路径,并创建assets文件夹(如果它不存在)。

现在只需像往常一样在Android Studio中创建一个本机应用程序的发布版本,您应该很好去!

怎么办?

此时,您可以照常继续开发您的应用。请参阅我们的调试和部署文档以了解有关使用React Native的更多信息。