为原生项目添加 RN 模块
你的大部分业务已经用原生代码实现,你想添加一些 RN 业务模块。
官方文档Integration with Existing Appsopen in new window,有比较详细的介绍,本文讲述的过程和官方文档大同小异。
为了确保流畅的体验,使用如下目录结构:
MyApp ├─ android/ ├─ ios/ ├─ node_modules/ ├─ package.json
创建 RN 项目
运行如下命令,创建一个 RN 项目:
npx react-native init <AppName>
也可以使用
npx react-native-create-app <AppName>
命令来创建
创建成功后,打开该目录, 删除里面的 andriod 和 ios 文件夹 。
cd 到 RN 项目,执行如下命令添加依赖:
yarn add hybrid-navigation
RN 项目配置
打开 index.js 这个文件,通常,它就在 package.json 旁边。
你需要注册你的 React 组件
以前,你是这么注册的
AppRegistry.registerComponent('ReactNativeProject', () => App)
现在,你需要作出改变
import { ReactRegistry, Garden, BarStyleDarkContent } from 'hybrid-navigation' import Home from './HomeComponent' import Profile from './ProfileComponent' // 配置全局样式 Garden.setStyle({ topBarStyle: BarStyleDarkContent, }) ReactRegistry.startRegisterComponent() // 注意,你的每一个页面都需要注册 ReactRegistry.registerComponent('Home', () => Home) ReactRegistry.registerComponent('Profile', () => Profile) ReactRegistry.endRegisterComponent()
Android 项目配置
首先,将现有 Android 项目拷贝到 RN 项目的 android 文件夹下。结构如下:
MyApp ├─ android/ │ ├─ app/ │ ├─ build.gradle │ └─ settings.gradle ├─ ios/ ├─ node_modules/ ├─ package.json
在 settings.gradle 中添加如下配置
rootProject.name = 'MyApp' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle") applyNativeModulesSettingsGradle(settings) include ':app'
在根项目的 build.gradle 文件中,确保以下配置或变更
ext { + minSdkVersion = 21 + targetSdkVersion = 30 + compileSdkVersion = 30 + buildToolsVersion = '30.0.2' } buildscript { repositories { + google() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:4.2.2' } } allprojects { repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") + } + mavenCentral { + // We don't want to fetch react-native from Maven Central as there are + // older versions over there. + content { + excludeGroup "com.facebook.react" + } + } + google() + maven { url 'https://www.jitpack.io' } } }
在 app/build.gradle 文件中,作如下变更
+ project.ext.react = [ + entryFile: "index.js", + enableHermes: false, + ] + apply from: "../../node_modules/react-native/react.gradle" + def jscFlavor = 'org.webkit:android-jsc:+' + def enableHermes = project.ext.react.get("enableHermes", false); android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion } } dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation project(':hybrid-navigation') + implementation "com.facebook.react:react-native:+" // From node_modules + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + if (enableHermes) { + def hermesPath = "../../node_modules/hermes-engine/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + } else { + implementation jscFlavor + } } + apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle") + applyNativeModulesAppBuildGradle(project)
在 android/gradle/wrapper/gradle-wrapper.properties 文件中,确保你使用了正确的 gradle wrapper 版本。
- distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip + distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
修改 MainApplication.java 文件。在你的项目中,可能叫其它名字。
public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); return packages; } @Override protected String getJSMainModuleName() { return "index"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); ReactBridgeManager bridgeManager = ReactBridgeManager.get(); bridgeManager.install(getReactNativeHost()); FLog.setMinimumLoggingLevel(FLog.INFO); } }
创建 ReactEntryActivity
,继承 ReactAppCompatActivity
。
可以叫其它名字
import com.reactnative.hybridnavigation.ReactAppCompatActivity; public class ReactEntryActivity extends ReactAppCompatActivity { @Override protected String getMainComponentName() { return "Home"; } }
如果希望 UI 层级由原生这边决定,则需要实现 onCreateMainComponent
方法:
@Override protected void onCreateMainComponent() { // 注意不要调用下面这行代码 // super.onCreateMainComponent(); ReactBridgeManager bridgeManager = getReactBridgeManager(); ReactStackFragment navigation = new ReactStackFragment(); navigation.setRootFragment(bridgeManager.createFragment("Navigation")); ReactStackFragment options = new ReactStackFragment(); options.setRootFragment(bridgeManager.createFragment("Options")); ReactTabBarFragment tabBarFragment = new ReactTabBarFragment(); tabBarFragment.setChildFragments(navigation, options); setActivityRootFragment(tabBarFragment); }
为 ReactEntryActivity
添加 NoActionBar 主题
<activity android:name=".ReactEntryActivity" android:theme="@style/Theme.AppCompat.NoActionBar" />
在 AndroidManifest.xml 中添加如下权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning"> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> </application>
iOS 项目配置
首先,将现有 iOS 项目拷贝到 RN 项目的 ios 文件夹下。结构如下:
MyApp ├─ android/ ├─ ios/ │ ├─ Podfile │ ├─ *.xcodeproj/ │ └─ *.xcworkspace/ ├─ node_modules/ ├─ package.json
假设你使用 cocopods 来管理依赖,在 Podfile 文件中添加如下设置
platform :ios, '11.0' require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target 'MyApp' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # to enable hermes on iOS, change `false` to `true` and then install pods :hermes_enabled => false ) end post_install do |installer| react_native_post_install(installer) __apply_Xcode_12_5_M1_post_install_workaround(installer) end
记得 pod install
一次。
找到 Info.plist 文件,右键 -> Open As -> Source Code,添加如下内容
<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>localhost</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict>
在 Build Phases 中新建一个 Run Script
双击标题,将其更名为 Bundle React Native code and images
点击三角图标展开,在其中填入
export NODE_BINARY=node ../node_modules/react-native/scripts/react-native-xcode.sh
像下面那样更改 AppDelegate.h 文件
#import <UIKit/UIKit.h> #import <React/RCTBridgeDelegate.h> @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate> @property (strong, nonatomic) UIWindow *window; @end
像下面那样更改 AppDelegate.m 文件
#import "AppDelegate.h" #import <React/RCTBundleURLProvider.h> #import <React/RCTBridgeModule.h> #import <HybridNavigation/HybridNavigation.h> @interface AppDelegate () <HBDReactBridgeManagerDelegate> @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; [[HBDReactBridgeManager get] installWithBridge:bridge]; UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil]; UIViewController *rootViewController = [storyboard instantiateInitialViewController]; self.window.windowLevel = UIWindowLevelStatusBar + 1; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; return YES; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif } - (void)reactModuleRegisterDidCompleted:(HBDReactBridgeManager *)manager { HBDTabBarController *tabs = [[HBDTabBarController alloc] init]; HBDNavigationController *navigation = [[HBDNavigationController alloc] initWithRootViewController:[manager controllerWithModuleName:@"Navigation" props:nil options:nil]]; HBDNavigationController *options = [[HBDNavigationController alloc] initWithRootViewController:[manager controllerWithModuleName:@"Options" props:nil options:nil]]; [tabs setViewControllers:@[ navigation, options ]]; [manager setRootViewController:tabs]; } @end