为原生项目添加 RN 模块
你的大部分业务已经用原生代码实现,你想添加一些 RN 业务模块。
为了确保流畅的体验,使用如下目录结构:
MyApp
├─ android/
├─ ios/
├─ node_modules/
├─ package.json
也可以使用其它目录结构,此时需要配置 RN 项目的 react-native.config.js 文件,将 android 和 ios 文件夹添加到 project 中。
创建 RN 项目
运行如下命令,创建一个 RN 项目:
npx @react-native-community/cli init <AppName>
也可以使用
npx react-native-create-app <AppName>命令来创建
创建成功后,打开该目录,删除里面的 android 和 ios 文件夹。
cd 到 RN 项目,执行如下命令添加依赖:
yarn add hybrid-navigation
RN 项目配置
打开 index.js 这个文件,通常,它就在 package.json 旁边。
你需要注册你的 React 组件
以前,你是这么注册的
AppRegistry.registerComponent('ReactNativeProject', () => App);
现在,你需要作出改变
import Navigation, { BarStyleDarkContent } from 'hybrid-navigation';
import Home from './HomeComponent';
import Profile from './ProfileComponent';
// 配置全局样式
Navigation.setDefaultOptions({
topBarStyle: BarStyleDarkContent,
});
Navigation.startRegisterComponent();
// 注意,你的每一个页面都需要注册
Navigation.registerComponent('Home', () => Home);
Navigation.registerComponent('Profile', () => Profile);
Navigation.endRegisterComponent();
// UI 层级由原生决定时,不要在 JS 里调用 Navigation.setRoot,由原生在适当时机设置根界面。
Android 项目配置
首先,将现有 Android 项目拷贝到 RN 项目的 android 文件夹下。结构如下:
MyApp
├─ android/
│ ├─ app/
│ ├─ build.gradle
│ └─ settings.gradle
├─ ios/
├─ node_modules/
├─ package.json
在 settings.gradle 中添加如下配置
pluginManagement {
includeBuild("../node_modules/@react-native/gradle-plugin")
}
plugins {
id("com.facebook.react.settings")
}
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex ->
ex.autolinkLibrariesFromCommand()
}
rootProject.name = 'MyApp'
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
在根项目的 build.gradle 文件中,确保以下配置或变更
buildscript {
ext {
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
apply plugin: "com.facebook.react.rootproject"
在 app/build.gradle 文件中,作如下变更
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
react {
/* Autolinking */
autolinkLibrariesWithApp()
}
def enableProguardInReleaseBuilds = false
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "com.myapp"
defaultConfig {
applicationId "com.myapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation("com.facebook.react:react-android")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
在 android/gradle/wrapper/gradle-wrapper.properties 文件中,确保你使用了正确的 gradle wrapper 版本。
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
注意:Gradle 版本需要与 React Native 版本兼容。对于 React Native 0.73+,建议使用 Gradle 9.0.0。
在 android/gradle.properties 文件中,确保以下配置:
# AndroidX package structure
android.useAndroidX=true
# Use this property to enable support to the new architecture.
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
hermesEnabled=true
# Use this property to enable edge-to-edge display support.
edgeToEdgeEnabled=true
修改 MainApplication.java 文件。在你的项目中,可能叫其它名字。
import android.app.Application;
import androidx.annotation.NonNull;
import com.facebook.common.logging.FLog;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactHost;
import com.facebook.react.ReactNativeApplicationEntryPoint;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultReactHost;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.reactnative.hybridnavigation.ReactManager;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost reactNativeHost = new DefaultReactNativeHost(this) {
@Override
public List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here
return packages;
}
@NonNull
@Override
public String getJSMainModuleName() {
return "index";
}
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
public boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
public boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@NonNull
@Override
public ReactHost getReactHost() {
return DefaultReactHost.getDefaultReactHost(getApplicationContext(), reactNativeHost, null);
}
@Override
public void onCreate() {
super.onCreate();
ReactNativeApplicationEntryPoint.loadReactNative(this);
ReactManager reactManager = ReactManager.get();
reactManager.install(getReactHost());
// register native modules
reactManager.registerNativeModule("NativeModule", NativeFragment.class);
FLog.setMinimumLoggingLevel(FLog.INFO);
}
}
创建 ReactEntryActivity,继承 ReactAppCompatActivity。
可以叫其它名字
import com.reactnative.hybridnavigation.ReactAppCompatActivity;
public class ReactEntryActivity extends ReactAppCompatActivity {
@Override
protected String getMainComponentName() {
return "Home";
}
}
如果希望 UI 层级由原生这边决定,则重写 onCreateMainComponent,用 getReactManager().createFragment(moduleName, props, options) 创建 RN/原生页面并组装成 Tab、Stack 等,最后调用 setActivityRootFragment。此时 index.js 中不要调用 Navigation.setRoot(可参考 react-native-toast-hybrid 的 index.ts)。
import com.navigation.androidx.StackFragment;
import com.navigation.androidx.TabBarItem;
import com.reactnative.hybridnavigation.HybridFragment;
import com.reactnative.hybridnavigation.ReactTabBarFragment;
@Override
protected void onCreateMainComponent() {
// 不要调用 super.onCreateMainComponent()
Bundle options1 = new Bundle();
Bundle titleItem1 = new Bundle();
titleItem1.putString("title", "React");
options1.putBundle("titleItem", titleItem1);
HybridFragment f1 = getReactManager().createFragment("Tab1", null, options1);
Bundle options2 = new Bundle();
Bundle titleItem2 = new Bundle();
titleItem2.putString("title", "Native");
options2.putBundle("titleItem", titleItem2);
HybridFragment f2 = getReactManager().createFragment("Tab2", null, options2);
StackFragment nav1 = new StackFragment();
nav1.setTabBarItem(new TabBarItem("React"));
nav1.setRootFragment(f1);
StackFragment nav2 = new StackFragment();
nav2.setTabBarItem(new TabBarItem("Native"));
nav2.setRootFragment(f2);
ReactTabBarFragment tabs = new ReactTabBarFragment();
tabs.setChildFragments(nav1, nav2);
setActivityRootFragment(tabs);
}
为 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
假设你使用 CocoaPods 来管理依赖,在 Podfile 文件中添加如下设置
# Disable New Architecture (optional)
# ENV['RCT_NEW_ARCH_ENABLED'] = '0'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, min_ios_version_supported
prepare_react_native_project!
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'MyApp' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true,
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
end
end
记得 pod install 一次。
找到 Info.plist 文件,右键 -> Open As -> Source Code,添加如下内容
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>RCTNewArchEnabled</key>
<true/>
注意:
NSAllowsArbitraryLoads设置为true仅用于开发环境,生产环境应该移除或设置为false。RCTNewArchEnabled用于启用新的 React Native 架构(Fabric 和 TurboModules)。
像下面那样更改 AppDelegate.h 文件
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
像下面那样更改 AppDelegate.m 文件
#import "AppDelegate.h"
#import <React-RCTAppDelegate/RCTDefaultReactNativeFactoryDelegate.h>
#import <React-RCTAppDelegate/RCTReactNativeFactory.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLog.h>
#import <React/RCTDevMenu.h>
#import <HybridNavigation/HybridNavigation.h>
#import "NativeViewController.h"
@interface ReactNativeDelegate : RCTDefaultReactNativeFactoryDelegate
@end
@implementation ReactNativeDelegate
- (NSURL *)bundleURL {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end
@interface AppDelegate () <HBDReactBridgeManagerDelegate>
@property (strong, nonatomic) RCTRootViewFactory *rootViewFactory;
@property (strong, nonatomic) id<RCTReactNativeFactoryDelegate> reactNativeDelegate;
@property (strong, nonatomic) RCTReactNativeFactory *reactNativeFactory;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
RCTSetLogThreshold(RCTLogLevelInfo);
ReactNativeDelegate *delegate = [[ReactNativeDelegate alloc] init];
RCTReactNativeFactory *factory = [[RCTReactNativeFactory alloc] initWithDelegate:delegate];
delegate.dependencyProvider = [[RCTAppDependencyProvider alloc] init];
self.reactNativeDelegate = delegate;
self.reactNativeFactory = factory;
self.rootViewFactory = factory.rootViewFactory;
[self.rootViewFactory initializeReactHostWithLaunchOptions:launchOptions bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
[[HBDReactBridgeManager get] installWithReactHost:self.rootViewFactory.reactHost];
// register native modules
[[HBDReactBridgeManager get] registerNativeModule:@"NativeModule" forViewController:[NativeViewController class]];
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;
}
- (void)reactModuleRegisterDidCompleted:(HBDReactBridgeManager *)manager {
// 由原生决定 UI 层级时在此设置根界面;此时 index.js 中不要调用 Navigation.setRoot(可参考 [react-native-toast-hybrid](https://github.com/listenzz/react-native-toast-hybrid) 的 [AppDelegate.mm](https://github.com/listenzz/react-native-toast-hybrid/blob/master/ios/ToastHybrid/AppDelegate.mm))
HBDViewController *vc1 = [manager viewControllerWithModuleName:@"Tab1" props:nil options:@{@"titleItem": @{@"title": @"React"}}];
HBDViewController *vc2 = [manager viewControllerWithModuleName:@"Tab2" props:nil options:@{@"titleItem": @{@"title": @"Native"}}];
HBDNavigationController *nav1 = [[HBDNavigationController alloc] initWithRootViewController:vc1];
nav1.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"React" image:nil selectedImage:nil];
HBDNavigationController *nav2 = [[HBDNavigationController alloc] initWithRootViewController:vc2];
nav2.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Native" image:nil selectedImage:nil];
HBDTabBarController *tabs = [[HBDTabBarController alloc] init];
[tabs setViewControllers:@[nav1, nav2]];
[manager setRootViewController:tabs];
}
// iOS 9.x or newer
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options];
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return [GlobalStyle globalStyle].interfaceOrientation;
}
@end