为原生项目添加 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-hybridopen in new windowindex.tsopen in new window)。

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 仅用于开发环境,生产环境应该移除或设置为 falseRCTNewArchEnabled 用于启用新的 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
上次更新: