React Native 应用适配鸿蒙PC 实战:从白屏到成功运行
三者的版本必须严格对齐。RNOH 的版本号对应了它所支持的 React Native 版本。例如 RNOH 0.82.30 对应 RN 0.82.x。如果 RN 版本过高,会出现 TurboModule 不兼容;版本过低,则可能出现 API 缺失。
React Native 应用适配鸿蒙PC 实战:从白屏到成功运行
背景
随着 HarmonyOS NEXT 的推出,越来越多的开发者希望将现有的 React Native 应用运行在鸿蒙平台上。React Native OpenHarmony(RNOH)项目为这一需求提供了框架支持,但在实际适配过程中,由于版本兼容性、架构差异、依赖缺失等问题,开发者往往会遇到各种阻碍。
本文记录了一个真实的 React Native 项目(AwesomeProject)适配 HarmonyOS 的完整过程,从项目创建到最终成功运行,期间经历了多次白屏问题及深度调试,最终逐一解决。希望通过这篇文章,能帮助更多开发者少走弯路,快速完成 RN 到鸿蒙的适配工作。
环境信息
| 项目 | 版本 |
|---|---|
| macOS | macOS Sonoma |
| DevEco Studio | 5.0+ |
| React Native | 0.82.1 |
| @rnoh/react-native-openharmony | 0.82.30 |
| @react-native-oh/react-native-harmony | 0.82.30 |
| @react-native-oh/react-native-harmony-cli | 0.82.30 |
| Metro | 0.83.7 |
| Hermes | 随 RN 0.82.1 内置 |
| HarmonyOS SDK | 6.1.1(24) |
| CAPI 架构 | RNOH_C_API_ARCH=1 |
第一步:环境准备
1.1 配置 HDC 工具环境变量
HDC(HarmonyOS Device Connector)是与鸿蒙设备通信的核心工具,需配置环境变量:
# 编辑 ~/.zshrc
vi ~/.zshrc
# 添加以下内容
export PATH="/Applications/DevEco-Studio.app/Contents/sdk/{版本路径}/openharmony/toolchains:$PATH"
HDC_SERVER_PORT=7035
launchctl setenv HDC_SERVER_PORT $HDC_SERVER_PORT
export HDC_SERVER_PORT
使配置生效:
source ~/.zshrc
1.2 配置 CAPI 版本环境变量
RNOH 框架的 Demo 工程默认使用 CAPI 版本,必须设置环境变量:
# 在 ~/.zshrc 中添加
export RNOH_C_API_ARCH=1
验证:
echo $RNOH_C_API_ARCH
# 输出应为:1
⚠️ 重要:此环境变量在构建和部署时必须生效,否则 CAPI 架构不会启用,导致白屏。
第二步:创建 React Native 项目
2.1 初始化 RN 项目
npx @react-native-community/cli init AwesomeProject
cd AwesomeProject
2.2 添加 OpenHarmony 依赖
修改 package.json,在 dependencies 中添加:
{
"dependencies": {
"@react-native-oh/react-native-harmony": "^0.82.30",
"@react-native-oh/react-native-harmony-cli": "^0.82.30",
"metro": "^0.83.7",
"react": "19.1.1",
"react-native": "0.82.1"
}
}
⚠️ 关键踩坑:
react-native版本必须与@rnoh/react-native-openharmony版本对齐。初始创建的 RN 项目可能是 0.84.x 版本,但 RNOH 0.82.30 仅兼容 RN 0.82.x,版本不匹配会导致 TurboModule 缺失错误。此外,metro需要作为直接依赖安装,否则bundle-harmony命令可能找不到 Metro。
安装依赖:
npm install
2.3 创建 Metro 配置
创建 metro.config.js:
const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config');
const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config');
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
module.exports = mergeConfig(
getDefaultConfig(__dirname),
createHarmonyMetroConfig({
reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
}),
config
);
2.4 简化 App.tsx
默认模板使用了 @react-native/new-app-screen 和 react-native-safe-area-context,这些组件库目前没有对应的 OHOS 适配版本,会导致运行时崩溃。将其替换为最基本的 RN 组件:
import { Text, View, StyleSheet } from 'react-native';
function App() {
return (
<View style={styles.container}>
<Text>Hello HarmonyOS!</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
2.5 生成 JS Bundle
在 package.json 的 scripts 中添加:
"dev": "react-native bundle-harmony --dev"
执行生成:
npx react-native bundle-harmony --dev false
成功后会在 harmony/entry/src/main/resources/rawfile/ 目录下生成 bundle.harmony.js 文件。
第三步:创建 HarmonyOS 项目
3.1 使用 DevEco Studio 创建项目
在 DevEco Studio 中创建一个新的 HarmonyOS 项目(Myrndemo),选择 Empty Ability 模板,配置如下:
- Bundle Name:
com.nutpi.rndemo - Module Name:
entry - Language: ArkTS
3.2 添加 RNOH 依赖
修改 entry/oh-package.json5:
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"@rnoh/react-native-openharmony": "0.82.30",
"@ppd/ffrt": "1.1.5"
},
"devDependencies": {},
"dynamicDependencies": {}
}
⚠️ 踩坑:
@ppd/ffrt是 RNOH C++ 层的运行时依赖,缺少它会导致 C++ 编译链接失败。
3.3 配置 CMakeLists.txt
创建或修改 entry/src/main/cpp/CMakeLists.txt:
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(RNOH_CPP_DIR "${OH_MODULE_DIR}/@rnoh/react-native-openharmony/src/main/cpp")
set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
add_compile_definitions(WITH_HITRACE_SYSTRACE)
set(WITH_HITRACE_SYSTRACE 1)
add_subdirectory("${RNOH_CPP_DIR}" ./rn)
add_library(rnoh_app SHARED
"./PackageProvider.cpp"
"${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)
3.4 创建 PackageProvider.cpp
创建 entry/src/main/cpp/PackageProvider.cpp:
#include "RNOH/PackageProvider.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {};
}
3.5 配置 EntryAbility
修改 entry/src/main/ets/entryability/EntryAbility.ets:
import { RNAbility } from '@rnoh/react-native-openharmony';
export default class EntryAbility extends RNAbility {
getPagePath() {
return 'pages/Index';
}
}
注意:EntryAbility 必须继承自
RNAbility而非UIAbility,否则 RN 运行时无法初始化。
3.6 创建 RNPackagesFactory
创建 entry/src/main/ets/RNPackagesFactory.ets:
import { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [];
}
3.7 配置 Index.ets(核心页面)
修改 entry/src/main/ets/pages/Index.ets:
import {
AnyJSBundleProvider,
ComponentBuilderContext,
FileJSBundleProvider,
MetroJSBundleProvider,
ResourceJSBundleProvider,
RNApp,
RNOHErrorDialog,
RNOHLogger,
TraceJSBundleProviderDecorator,
RNOHCoreContext
} from '@rnoh/react-native-openharmony';
import { createRNPackages } from '../RNPackagesFactory';
@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {}
const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent)
@Entry
@Component
struct Index {
@StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined
@State shouldShow: boolean = false
private logger!: RNOHLogger
aboutToAppear() {
this.logger = this.rnohCoreContext!.logger.clone("Index")
const stopTracing = this.logger.clone("aboutToAppear").startTracing();
this.shouldShow = true
stopTracing();
}
onBackPress(): boolean | undefined {
this.rnohCoreContext!.dispatchBackPress()
return true
}
build() {
Column() {
if (this.rnohCoreContext && this.shouldShow) {
if (this.rnohCoreContext?.isDebugModeEnabled) {
RNOHErrorDialog({ ctx: this.rnohCoreContext })
}
RNApp({
rnInstanceConfig: {
createRNPackages,
enableNDKTextMeasuring: true,
enableBackgroundExecutor: false,
enableCAPIArchitecture: true,
arkTsComponentNames: []
},
initialProps: { "foo": "bar" } as Record<string, string>,
appKey: "AwesomeProject",
wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
onSetUp: (rnInstance) => {
rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
},
jsBundleProvider: new TraceJSBundleProviderDecorator(
new AnyJSBundleProvider([
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js'),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
new MetroJSBundleProvider(),
]),
this.rnohCoreContext.logger),
})
}
}
.height('100%')
.width('100%')
}
}
关键配置说明:
| 配置项 | 说明 |
|---|---|
enableCAPIArchitecture: true |
启用 CAPI 架构,需配合 RNOH_C_API_ARCH=1 环境变量 |
appKey: "AwesomeProject" |
必须与 JS 端 AppRegistry.registerComponent 注册的名称一致 |
ResourceJSBundleProvider 优先 |
确保 bundle 从本地资源加载,而非先尝试 Metro 连接 |
3.8 配置构建选项
确认 entry/build-profile.json5 中包含 C++ 构建配置:
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
},
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": ""
}
}
}
第四步:拷贝 JS Bundle 并构建
4.1 拷贝 Bundle
将 RN 项目生成的 bundle 拷贝到 HarmonyOS 项目的 rawfile 目录:
cp AwesomeProject/harmony/entry/src/main/resources/rawfile/bundle.harmony.js \
Myrndemo/entry/src/main/resources/rawfile/bundle.harmony.js
4.2 安装 OH 依赖
在 Myrndemo 根目录执行:
ohpm install
4.3 构建项目
export RNOH_C_API_ARCH=1
cd Myrndemo
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--mode module -p module=entry@default -p product=default assembleHap --no-daemon
第五步:部署与调试
5.1 安装到设备
hdc install Myrndemo/entry/build/default/outputs/default/entry-default-unsigned.hap
5.2 启动应用
hdc shell aa start -a EntryAbility -b com.nutpi.rndemo

5.3 查看日志
# 清除旧日志
hdc shell hilog -r
# 查看 RNOH 相关日志
hdc shell hilog -x | grep "#RNOH"
踩坑记录:从白屏到成功运行的完整排障过程
问题一:首次白屏 — 无任何 RNOH 日志输出
现象:应用启动后白屏,hdc hilog 中没有任何 #RNOH 日志。
原因:MetroJSBundleProvider 在 JS Bundle Provider 列表中排在第一位。当设备没有连接 Metro 开发服务器时,Metro 连接失败导致整个 Bundle 加载流程中断,后续的 ResourceJSBundleProvider 也未被执行。
修复:调整 Index.ets 中 AnyJSBundleProvider 的顺序,将 ResourceJSBundleProvider 放在第一位:
// 修复前(错误顺序)
new AnyJSBundleProvider([
new MetroJSBundleProvider(), // ❌ 优先尝试 Metro,离线必失败
new ResourceJSBundleProvider(...), // 永远不会执行到
])
// 修复后(正确顺序)
new AnyJSBundleProvider([
new ResourceJSBundleProvider(..., 'bundle.harmony.js'), // ✅ 优先从本地加载
new ResourceJSBundleProvider(..., 'hermes_bundle.hbc'), // 备选 HBC 格式
new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
new MetroJSBundleProvider(), // 最后尝试 Metro(开发调试用)
])
验证:重新构建部署后,日志中出现 RNOH 初始化的 ASCII Art 标志和 TurboModule 创建日志。
问题二:RNOH 初始化后报 TurboModule 缺失
现象:日志中出现以下错误:
E #RNOH_CPP: Couldn't provide turbo module "NativePerformanceCxx"
E #RNOH_CPP: Couldn't provide turbo module "RNCSafeAreaContext"
原因:RN 0.84.1 引入了 NativePerformanceCxx TurboModule,但 RNOH 0.82.30 尚未实现该模块。同时,@react-native/new-app-screen 依赖了 react-native-safe-area-context,该库没有 OHOS 适配。
修复:分两步解决:
- 降级 React Native 版本:将
react-native从 0.84.1 降级到 0.82.1,与 RNOH 0.82.30 对齐:
// package.json
{
"dependencies": {
"react-native": "0.82.1",
"react": "19.1.1"
}
}
- 移除不兼容的组件库:将
App.tsx从使用@react-native/new-app-screen和react-native-safe-area-context的模板代码,替换为纯react-native基础组件(View + Text)。
验证:重新生成 bundle 后,RNCSafeAreaContext 错误消失。NativePerformanceCxx 的警告仍然存在但为非致命错误,不影响渲染。
问题三:C++ 编译缺少 @ppd/ffrt 依赖
现象:构建时报链接错误,找不到 ffrt 相关符号。
原因:RNOH 的 C++ 层依赖 @ppd/ffrt(Functional Flow Runtime),但 oh-package.json5 中未声明该依赖。
修复:在 entry/oh-package.json5 的 dependencies 中添加:
{
"dependencies": {
"@rnoh/react-native-openharmony": "0.82.30",
"@ppd/ffrt": "1.1.5"
}
}
问题四:持续白屏 — RNOH 已初始化但无 UI 渲染
现象:RNOH 日志显示初始化成功,Running "AwesomeProject" 出现,但屏幕仍然白屏,没有 shadow tree 创建日志。
原因:此问题由多个因素叠加导致:
-
App.tsx 使用了不兼容组件:
@react-native/new-app-screen内部使用了 OHOS 不支持的组件(如SafeAreaView),导致 JS 执行时虽然AppRegistry.registerComponent成功注册,但渲染阶段组件树构建失败。 -
RNOH_C_API_ARCH环境变量未设置:CAPI 架构需要在构建时设置此环境变量,否则 C++ 层的 CAPI 渲染管线不会正确初始化。
修复:
- 简化
App.tsx为仅使用View+Text的最小组件 - 在
~/.zshrc中添加export RNOH_C_API_ARCH=1并source - 确保构建命令在设置了
RNOH_C_API_ARCH=1的环境中执行
问题五:Metro 依赖缺失导致 bundle 命令失败
现象:执行 npx react-native bundle-harmony 时报错,提示找不到 Metro。
原因:metro 作为 @react-native-community/cli 的间接依赖,在某些版本中不会被自动安装为直接依赖,而 bundle-harmony 命令需要直接引用 Metro。
修复:在 package.json 的 dependencies 中显式添加 metro:
{
"dependencies": {
"metro": "^0.83.7"
}
}
最终成功的日志标志
当所有问题解决后,完整的成功日志如下:
I #RNOH_ARK: ██████╗ ███╗ ██╗ ██████╗ ██╗ ██╗
I #RNOH_ARK: ██╔══██╗████╗ ██║██╔═══██╗██║ ██║
I #RNOH_ARK: ██████╔╝██╔██╗ ██║██║ ██║███████║
I #RNOH_ARK: ██╔══██╗██║╚██╗██║██║ ██║██╔══██║
I #RNOH_ARK: ██║ ██║██║ ╚████║╚██████╔╝██║ ██║
I #RNOH_ARK: ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
I #RNOH_CPP: RNOHAppNapiBridge.cpp:131> onInit (LOG_VERBOSITY_LEVEL=0)
I #RNOH_CPP: Using HermesInstance
I #RNOH_CPP: RNInstanceInternal::start
D #RNOH_ARK: RNInstance::runJSBundle START
D #RNOH_ARK: RNInstance::runJSBundle STOP
I #RNOH_JS: Running "AwesomeProject"
I #RNOH_CPP: Creating Turbo Module: DeviceInfo
I #RNOH_CPP: Creating Turbo Module: BlobModule
I #RNOH_CPP: Creating Turbo Module: DeviceEventManager
I #RNOH_CPP: ArkUISurface.cpp:74> onMessageReceived: STATUS_BAR_HEIGHT
I #RNOH_CPP: ArkUISurface.cpp:74> onMessageReceived: CONFIGURATION_UPDATE
I #RNOH_CPP: ArkUISurface.cpp:74> onMessageReceived: WINDOW_SIZE_CHANGE
关键成功标志:
- ✅ RNOH ASCII Art 横幅出现
- ✅
Using HermesInstance— Hermes 引擎启动 - ✅
Running "AwesomeProject"— JS Bundle 执行成功,AppRegistry 找到注册的组件 - ✅
ArkUISurface收到STATUS_BAR_HEIGHT/CONFIGURATION_UPDATE/WINDOW_SIZE_CHANGE— UI 渲染管线工作正常
可忽略的非致命警告
以下警告在日志中出现但不会影响应用运行:
| 警告 | 说明 |
|---|---|
Failed to get customDensity: {} |
自定义密度未配置,使用默认值即可 |
Shake to open Dev Menu is disabled |
缺少加速度计权限,不影响功能 |
Failed to get maxFontScale |
最大字体缩放获取失败,使用默认值 |
Turbo Module 'NativePerformanceCxx' not found |
RN 0.82.x 的性能模块,RNOH 未实现,降级处理即可 |
Turbo Module 'SoundManager' not found |
音效管理器,RNOH 未实现,不影响核心功能 |
Turbo Module 'FrameRateLogger' not found |
帧率日志,RNOH 未实现,不影响核心功能 |
完整项目结构
reactnative/
├── AwesomeProject/ # React Native 项目
│ ├── App.tsx # 简化后的入口组件
│ ├── package.json # 含 RNOH 依赖
│ ├── metro.config.js # HarmonyOS Metro 配置
│ └── harmony/
│ └── entry/src/main/resources/rawfile/
│ └── bundle.harmony.js # 生成的 JS Bundle
│
└── Myrndemo/ # HarmonyOS 项目
├── AppScope/app.json5 # 应用配置(bundleName: com.nutpi.rndemo)
├── build-profile.json5 # 构建配置
├── oh-package.json5 # 项目级依赖
├── entry/
│ ├── build-profile.json5 # 模块构建配置(含 CMake)
│ ├── oh-package.json5 # 模块依赖(RNOH + ffrt)
│ └── src/main/
│ ├── cpp/
│ │ ├── CMakeLists.txt # C++ 构建配置
│ │ └── PackageProvider.cpp # C++ Package 提供者
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 继承 RNAbility
│ │ ├── RNPackagesFactory.ets # RN Package 工厂
│ │ └── pages/
│ │ └── Index.ets # 主页面(RNApp + RNSurface)
│ ├── module.json5 # 模块配置
│ └── resources/rawfile/
│ └── bundle.harmony.js # JS Bundle(从 RN 项目拷贝)
└── oh_modules/ # OH 依赖安装目录
关键经验总结
1. 版本对齐是第一优先级
React Native、@rnoh/react-native-openharmony、@react-native-oh/react-native-harmony 三者的版本必须严格对齐。RNOH 的版本号对应了它所支持的 React Native 版本。例如 RNOH 0.82.30 对应 RN 0.82.x。如果 RN 版本过高,会出现 TurboModule 不兼容;版本过低,则可能出现 API 缺失。
2. JS Bundle Provider 顺序至关重要
AnyJSBundleProvider 按列表顺序尝试加载 bundle,第一个成功的 provider 会终止后续尝试。在离线部署场景中,ResourceJSBundleProvider 必须排在 MetroJSBundleProvider 之前,否则 Metro 连接超时会阻塞整个加载流程。
3. CAPI 架构需要双重启用
CAPI 架构需要在两个地方同时启用:
Index.ets中rnInstanceConfig.enableCAPIArchitecture: true- 构建时环境变量
RNOH_C_API_ARCH=1
缺少任何一个都可能导致渲染管线异常。
4. 不兼容的第三方库是白屏的常见原因
在 OHOS 适配初期,应尽量简化 RN 端代码,只使用 react-native 核心组件(View、Text、ScrollView、Image 等)。对于使用了第三方库的模板代码(如 @react-native/new-app-screen、react-native-safe-area-context),需要确认其是否有对应的 OHOS 适配版本。没有适配版本的库会导致 JS 端渲染失败,表现为白屏。
5. 善用 hdc hilog 排查问题
hdc shell hilog -x | grep "#RNOH" 是最重要的调试手段。根据日志中的不同标记可以判断问题层级:
| 标记 | 含义 |
|---|---|
#RNOH_ARK |
ArkTS 层日志 |
#RNOH_CPP |
C++ 层日志 |
#RNOH_JS |
JavaScript 层日志 |
如果完全没有 #RNOH 日志,说明 RN 运行时根本没有初始化;如果有初始化日志但没有 Running "xxx" ,说明 JS Bundle 加载或执行失败;如果有 Running "xxx" 但白屏,说明组件渲染阶段出错。
下一步
在成功跑通基础 Demo 后,可以逐步:
- 添加更多 RN 核心组件:Image、ScrollView、TextInput、FlatList 等
- 适配第三方库:确认所需库是否有 OHOS 版本,参考 RNOH 生态兼容列表
- 接入 Metro 热更新:开发阶段连接 Metro 服务器实现热重载
- 性能优化:使用 Hermes Bytecode(HBC)格式 bundle 提升加载速度
- 发布构建:配置签名和混淆规则,生成 Release 版本
参考资料
更多推荐




所有评论(0)