1. 为什么要在 React Native 里做鸿蒙组件
先说个现实问题:很多团队前几年选了 React Native 做跨端方案,JS 代码同时跑 iOS 和 Android,靠的就是 RN 把 JS 渲染成原生组件。现在鸿蒙(HarmonyOS)的市场盘子越来越大,尤其是 HarmonyOS NEXT 纯血版出来后,不再兼容 Android APK,这个时候你就得面对一个残酷的事实——原来的 RN 应用没法直接在鸿蒙上跑。
这不是危言耸听。RN 底层依赖的是一套完整的原生渲染引擎和组件映射,HarmonyOS 没有 Android 的 View 系统,也没有 iOS 的 UIKit,它自己的 UI 框架是 ArkUI。RN 官方至今没有发布支持鸿蒙的版本,所以要在鸿蒙生态里继续复用 RN 的业务代码,就必须走适配层的路。目前社区最成熟的方案是 react-native-harmony(也叫 react-native-openharmony),它是 openharmony 社区维护的一个 RN 适配套件,用 harmony 的 C-API 和 ArkUI 组件去实现 RN 的渲染协议和原生模块调用协议。
这篇文章我按自己的实操经历,把"在 React Native 中开发鸿蒙组件"这件事从头到尾拆一遍。内容适合三类人:一是已经在用 RN 做业务、正准备适配鸿蒙的团队技术负责人;二是刚接触鸿蒙开发、想了解鸿蒙技术栈底子的前端开发者;三是对跨端渲染原理感兴趣、想搞明白 RN 和 ArkUI 到底怎么打通的技术爱好者。
我默认你已经有 RN 基础,懂 JS/TS,至少写过组件;鸿蒙部分我会从零讲起,因为很多 RN 的同学对 HarmonyOS 的工程结构、ArkTS 语法、Stage 模型这些完全不熟,这块不补上是没法继续往下走的。
2. 先搞懂鸿蒙开发的核心概念
2.1 ArkTS 和 ArkUI 到底是个啥
HarmonyOS 应用开发的语言是 ArkTS,它是 TypeScript 的超集,在 TS 基础上加了一套 ArkUI 的声明式 UI 语法。如果你写过 React 或 Vue,看到 ArkUI 的代码会觉得非常亲切。比如你要在页面上显示一个文本,React 里是<Text>Hello</Text>,ArkUI 里是Text('Hello');你要管理一个状态,React 里是useState,ArkUI 里是@State。
ArkUI 的组件树也是声明式结构,用build()方法返回值渲染 UI。一个最简单的页面长这样:
@Entry @Component struct HelloPage { @State message: string = 'Hello HarmonyOS'; build() { Column({ space: 10 }) { Text(this.message) .fontSize(30) .fontWeight(FontWeight.Bold) Button('点我') .onClick(() => { this.message = '你点了按钮'; }) } .width('100%') .padding(20) } }注意几个关键词:@Entry表示这是页面入口,@Component表示这是一个自定义组件,@State声明的变量变化时,UI 会自动重新渲染。这套响应式状态管理和 React 的思路几乎一模一样,只是写法不同。RN 开发者上手 ArkUI 基本没有障碍,真正需要花时间的是了解它有哪些内置组件、布局容器怎么用、事件怎么绑定。
ArkUI 的布局容器主要是Column(垂直排列)、Row(水平排列)、Stack(堆叠)、RelativeContainer(相对布局)等,类似 RN 的 View 加上 flexDirection 的组合。常用组件有Text、Image、Button、TextInput、List、Scroll、Stack等,基本能覆盖日常开发需求。
2.2 Stage 模型和 UIAbility 是理解鸿蒙工程的钥匙
鸿蒙应用从 API 9 开始全面推行 Stage 模型,替代早期的 FA(Feature Ability)模型。Stage 模型下,一个应用由多个 Module 组成,每个 Module 里可以有多个 UIAbility 和普通页面。UIAbility 是应用的一个界面入口单元,可以简单理解成"一个有独立生命周期的页面或窗口"。
为什么 RN 适配鸿蒙必须理解 Stage 模型?因为 RN 的容器本身就是一个 UIAbility。react-native-harmony 适配套件实现了一个RNAbility,它继承自鸿蒙的UIAbility,在这个 Ability 的onWindowStageCreate生命周期回调里初始化 RN 运行时,把 JS 组件渲染到 ArkUI 的容器里。
一个 RN 鸿蒙应用的 Module 结构大致是:
entry/ ├── src/main/ │ ├── ets/ │ │ ├── entryability/ │ │ │ └── EntryAbility.ets // 应用入口 UIAbility │ │ ├── pages/ │ │ │ └── Index.ets // 页面入口 │ │ └── rn/ │ │ └── RNAbility.ets // RN 容器 Ability │ ├── resources/ │ └── module.json5 // 模块配置和权限声明module.json5是鸿蒙模块的配置文件,类似 Android 的AndroidManifest.xml。你要声明一个 UIAbility 能启动,就得在这里注册它。react-native-harmony 的脚手架会自动生成这些配置,但你要知道每个文件是干嘛的,否则出问题的时候无从下手。
2.3 分布式能力和鸿蒙设备生态
鸿蒙和其他操作系统最不一样的特点就是分布式能力。简单说,鸿蒙应用可以跨设备协同——手机上跑的业务可以流转到平板、手表、车机上,前提是设备之间组成了超级终端。这个特性对 RN 开发者意味着什么?
意味着你在鸿蒙上做 RN 组件时,不能只考虑"能跑",还要考虑"多设备适配"。比如window尺寸在不同设备上不一样,safe area的计算逻辑不同,甚至横竖屏切换的行为也不同。react-native-harmony 目前对多设备的适配还处在早期阶段,手机和平板的表现基本 OK,但手表和车机这种超小屏和特殊交互的设备,建议先不要上 RN,用纯 ArkUI 单独开发。
3. React Native 在鸿蒙上的运行原理
3.1 RN 的架构拆解:JS、Bridge 和原生渲染
要理解"RN 适配鸿蒙"这件事的难度,得先把 RN 的底层架构看清楚。React Native 的核心可以分为三层:
- JS 层:你写的业务代码,运行在 JavaScript 引擎里(默认是 Hermes,也可以切换 JSC)。
- Bridge 层(老架构)或 JSI(新架构):负责 JS 和原生之间的通信。老架构是异步序列化消息队列,性能一般;新架构是直接共享内存的接口调用,快很多。
- 原生层:iOS 上的 UIKit、Android 上的 View,RN 定义了一套组件映射协议,JS 里的
<View>会映射到原生UIView或ViewGroup,JS 里的<Text>映射到原生文本控件,原生控件负责真正的渲染和事件响应。
新架构(Fabric + TurboModule)下,RN 用 C++ 实现了共享的渲染层,通过 JSI(JavaScript Interface)让 JS 直接调用 C++,再通过 C++ 调用原生平台 API。这套设计的价值在于,只要你在某个平台上实现了一组 C++ 层定义好的接口,JS 层几乎不用改,就能跑起来。
3.2 为什么鸿蒙需要一层"适配壳"
HarmonyOS NEXT 没有 Android View,也没有 iOS UIKit,它自己的 UI 体系是 ArkUI 的组件树。RN 官方没有针对鸿蒙实现那一组原生接口,所以 RN 代码在鸿蒙上根本跑不起来。解决办法就是社区做的 react-native-harmony 适配层。
它的核心思路是:用鸿蒙的 C-API(N-API)和 ArkUI 组件去实现 RN 的 JSI 接口。RN 说"我要渲染一个 Text",适配层就在 ArkUI 那边创建一个Text('...')组件;RN 说"我要调用一个原生模块的方法",适配层通过 JSI 接口把调用转成 ArkUI 侧的能力。
这个方案的巧妙之处在于,业务层 JS 代码完全复用,不需要针对鸿蒙重写 UI。你要做的只是把适配层的原生工程集成进来,然后针对鸿蒙做小范围的兼容性调试。
3.3 老架构和新架构,选哪边
react-native-harmony 目前对新架构(New Architecture)的支持已经从实验走向可用。我在实际项目中用 0.72 以上的版本,默认开启新架构,配合新架构的 TurboModule 和 Fabric 组件,整体性能比老架构好不少。
如果你的项目还在用 RN 0.6x 的老版本,迁移到鸿蒙之前最好先考虑升级 RN 版本,因为鸿蒙适配层的维护重心已经全部转移到新架构上。老架构的 Bridge 在新平台上对接成本高、性能差,社区已经明确不会再花力气优化。
4. 实操:搭建 React Native + 鸿蒙开发环境
4.1 工具链准备
先说结论:环境搭建是鸿蒙 RN 开发里最容易卡住人的地方,因为涉及的工具链比单纯做 RN 或单纯做鸿蒙都要多。你需要装的东西如下:
- Node.js(建议 18 以上,RN 0.72+ 对 Node 版本有要求)
- React Native CLI 工具链(Java SDK、Android SDK,虽然最后不是跑 Android,但 RN 脚手架初始化工程时会用到)
- DevEco Studio(华为官方的 IDE,基于 IntelliJ,类似 Android Studio)
- HarmonyOS SDK(DevEco Studio 会自动下载,包括 API 版本选择)
- ohpm(鸿蒙的包管理器,类似 npm,用于安装鸿蒙原生依赖)
- hdc(HarmonyOS 设备连接工具,类似 adb,用于安装应用和查看日志)
版本对应关系建议查 react-native-harmony 官方仓库的 README,它有一个 compatibility 表格,列清楚了每个 RN 版本对应哪个 harmony 版本、哪个 SDK API Level。我用的组合是 RN 0.72.5 + harmony SDK 5.0.0(API 12),整体比较稳定。
4.2 初始化一个 RN 项目
先创建 RN 项目:
npx react-native init RnHarmonyDemo cd RnHarmonyDemo这和平时创建 RN 项目没有任何区别。接下来要装鸿蒙适配相关的依赖。react-native-harmony 提供了脚手架命令,会自动在 RN 项目里生成鸿蒙工程目录:
npm install react-native-harmony npx rnoh initrnoh init会在项目根目录生成harmony目录,里面是一个完整的 DevEco Studio 工程。你可以尝试用 DevEco Studio 打开这个目录,第一次打开会自动同步 Gradle 依赖和 ohpm 依赖,这个过程比较慢,耐心等。
4.3 配置签名和真机调试
鸿蒙应用跑在真机上必须签名,和 iOS 类似。DevEco Studio 里可以配置自动签名,但需要登录华为账号并开通个人开发者证书。团队开发建议用手动签名,把证书和 Profile 文件放到build-profile.json5里共享。
配置好签名后,把手机开启开发者模式,用 USB 连接电脑,通过 hdc 命令确认设备状态:
hdc list targets然后直接点 DevEco Studio 的运行按钮,应用就会安装到手机并启动。首次启动出现白屏是很常见的情况,这个我后面会在问题排查部分单独讲。
4.4 模拟器方案
鸿蒙也有官方模拟器,在 DevEco Studio 的 Device Manager 里可以创建。不过模拟器的性能表现一般,RN 应用在模拟器上跑起来会比较卡,尤其是 debug 模式下 JS 加载本身就有一定开销。如果你是做异步开发调试、没有真机在手边,模拟器可以用,但涉及性能评估和手势交互测试,强烈建议上真机。
社区还有一种方案是把鸿蒙系统跑在通用设备或第三方模拟器上,但稳定性和可复现性都不如官方模拟器,不建议在生产环境依赖这类方案。
5. 实战:开发第一个鸿蒙原生组件
5.1 了解两种组件形态:UI 组件和原生模块
在 RN 里,原生能力通常分两类:
- 原生模块(Native Module):提供方法供 JS 调用,比如读取设备信息、调用系统能力。鸿蒙侧对应的是一个用 ArkTS 写的模块,通过 C-API 暴露给 RN。
- 原生 UI 组件(Native UI Component):封装一个原生控件到 RN 里,比如一个只存在于鸿蒙上的特殊组件。鸿蒙侧对应的是一个 ArkUI 组件,通过 Fabric 组件协议注册给 RN。
react-native-harmony 提供了一套规范接口。新手先别急着手写,因为这类工作需要同时懂 ArkTS/ArkUI 和 RN 的 C++ 接口。通常的做法是先创建一个 ArkTS 模块,然后在 JS 侧通过TurboModule注册声明,最后在原生侧实现接口。逻辑上类似安卓端的ReactPackage+NativeModule。
5.2 用现有系统能力先跑通:以 Toast 为例
我建议你第一次做鸿蒙原生能力时,先挑一个最简单的模块练手,比如 Toast。步骤如下:
在鸿蒙工程里创建 Toast 模块,用 ArkTS 实现一个类:
import { promptAction } from '@kit.ArkUI'; export class ToastModule { show(message: string) { promptAction.showToast({ message: message }); } }然后在 RN 侧声明 TurboModule:
import { TurboModule, TurboModuleRegistry } from 'react-native'; export interface Spec extends TurboModule { show(message: string): void; } export default TurboModuleRegistry.getEnforcing<Spec>('ToastModule');接着通过 codegen 生成两端接口代码,并在鸿蒙侧实现 Native 接口,注册到 RNAbility。完成这一步,JS 里就能直接Toast.show('hello harmony')了。这里会有一些 C++ 和 ArkTS 的桥接样板代码,官方模板里有现成示例,直接参考复制即可。
5.3 开发自定义 UI 组件:把 ArkUI 组件封装给 RN
如果你想把自己写的 ArkUI 组件暴露给 RN 使用,比如一个自定义图表,流程会更复杂。大致的路径是:
在 ArkUI 侧写一个组件:
@Component export struct MyChart { @Prop data: number[] = []; build() { // 绘制图表 } }然后通过 Fabric 的 ComponentDescriptor 和 ComponentView 机制,把这个组件注册为 RN 可用的组件。RN 侧把它当成普通自定义组件使用:
import { requireNativeComponent } from 'react-native'; const MyChartView = requireNativeComponent('MyChart'); <MyChartView data={[1, 2, 3]} />;这一步的难点在于 Fabric 的线程模型和事件分发机制。虽然官方脚手架已经帮你处理了大部分模板代码,但遇到自定义事件、布局更新等场景,你还是要理解 Fabric 的State和EventEmitter机制。建议先跑通简单组件,再逐步增加交互复杂度。
5.4 组件开发和纯 ArkUI 开发的成本对比
很多团队会纠结:既然都上鸿蒙了,为什么不直接用 ArkUI 重写界面,非要套一层 RN?我的看法是:看业务存量和你想要什么。
如果你的业务逻辑大量在 JS 侧,且跨端团队规模不大,用 RN 适配鸿蒙能省下重写 UI 的大量工作量。如果你的应用在鸿蒙上需要深度体验分布式能力(比如跨设备流转、意图框架、原子化服务),建议直接用 ArkUI 开发核心功能,因为这类能力 RN 适配层还没完全打通,硬塞会很痛苦。更合理的策略是混合架构:通用页面走 RN,需要深度系统能力的鸿蒙专属页面走 ArkUI,通过 Router 跳转互相通信。
6. 常见问题与排查技巧实录
6.1 启动白屏:现象、原因和解决办法
最有代表性的问题就是启动白屏,网上搜"react native 启动白屏"有一堆案例。在鸿蒙场景下,白屏的原因比 Android/iOS 更复杂,我列一下我遇到过的几类:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 应用启动后一直白屏,hdc log 无 JS 报错 | debug 模式下 Metro 服务未启动或无法连接 | 先启动 Metro(npm start),确认手机和电脑在同一网络,设置DevSettings里的 debug server host |
白屏且日志提示Unable to load script | JS Bundle 未打包或路径错误 | release 模式下执行npx react-native bundle生成 bundle,确保assets目录正确 |
| 白屏但 ArkUI 侧日志正常 | RN 容器渲染时机问题,onWindowStageCreate调用太早 | 检查RNAbility是否在windowStage.loadContent的回调里初始化 RN 容器 |
| 启动后黑屏或闪烁 | 主题资源和启动图配置缺失 | 在resources/base/element/color.json和resources/base/profile里配置启动页颜色和背景图 |
白屏排查有个诀窍:不要只盯着业务 JS 代码,先分清楚鸿蒙原生侧是否已经渲染出来。如果原生侧 ArkUI 的日志都有,说明问题在 JS bundle 加载;如果原生侧也没反应,就要检查 Ability 的生命周期和签名配置。
6.2 依赖和构建问题
鸿蒙工程首次同步依赖,经常会出现 ohpm 依赖拉不下来、或者 Gradle 和 HarmonyOS SDK 版本不匹配的问题。我的经验是:不要盲目升级版本,先看官方仓库的docs/和CHANGELOG,找到和你 RN 版本匹配的适配版本组合。另外建议给 ohpm 配置国内镜像仓库,因为默认源在构建时可能很慢,甚至超时失败。
还有一类问题是 Java SDK 版本冲突。DevEco Studio 内置了 JBR(JetBrains Runtime),但 RN 的 Gradle 构建可能依赖系统 Java。如果你遇到Unsupported class file major version之类的报错,检查一下JAVA_HOME是否指向 DevEco Studio 自带的 JBR 目录。
6.3 性能调优:首屏耗时、列表卡顿和内存占用
RN 在鸿蒙上的性能,目前还不能说完全达到 Android 的水平。实测下来,首屏渲染耗时比 Android 多出 200~400ms,主要在组件树映射和 ArkUI 布局计算上。列表组件在数据量超过 1000 条时,滚动帧率会出现明显波动。
有几个坑提前踩过,分享出来:
- 避免在 JS 里做高频 setState 触发大组件树重渲染,ArkUI 的重渲染开销比 RN 在新架构下更敏感。
FlatList在大列表场景下性能不佳,优先尝试鸿蒙原生的Scroll+ForEach封装成自定义组件。- 减少
console.log。鸿蒙的调试日志通道比 Android 慢,大量日志会拖慢 JS 执行。 - 合理使用
InteractionManager和requestAnimationFrame,把非关键渲染任务延后。
内存方面,Hermes 在鸿蒙上运行正常,但如果你用的是 JSC 引擎,建议切换到 Hermes,具体配置在metro.config.js和原生工程的 gradle 参数里设置。
6.4 调试工具链:hdc、DevTools 和日志
调试是鸿蒙 RN 开发里最容易让人抓狂的环节。鸿蒙没有 Android Studio 那样的 Logcat,也没有 Xcode 的 Console,但好在 hdc 提供了足够多的能力:
hdc shell hilog # 查看应用日志 hdc shell param get const.product.model # 查看设备型号 hdc install <hap文件路径> # 安装应用 hdc shell aa start -a EntryAbility -b com.example.app # 启动应用RN 侧可以通过 Metro 的 DevTools 调试 JS 代码,在 DevEco Studio 里打开 Chrome DevTools 连接,断点调试和console.log都能用。有一点要注意:鸿蒙的 debug 模式默认使用 Metro 的热更新,但有时修改原生 ArkTS 代码后热更新不会触发,必须重新构建安装,这是正常现象,别浪费时间找配置问题。
6.5 版本升级踩坑
react-native-harmony 迭代很快,我见过很多人从 0.71 升到 0.72 就崩了,原因大多是原生工程目录结构变了,或者 SDK API 版本不匹配。升级前务必看官方迁移文档,别直接改package.json就跑npm install。有个小技巧:先把原来的harmony目录重命名备份,再重新npx rnoh init生成一份干净的工程,然后把你自己改过的地方对照迁移文档手动合入。这样虽然麻烦,但比在旧工程上强行升级要可控得多。
7. 多一些想法:这套方案后续怎么走
开发层面讲得差不多了,最后想聊聊我对这个技术方向的真实感受。react-native-harmony 这个适配层,本质上是把 RN 的"一次编写,处处运行"的目标继续往前推了一步。鸿蒙从系统层面不再兼容 Android 后,所有跨端方案都必须重新回答"你这个平台到底适配不适配"这个问题。RN 因为社区活跃度高,生态和工具链完整的优势,目前来看是跑在最前面的跨端方案之一。
但我必须实事求是地讲,这套方案现在还不能说完美。组件生态的覆盖度、ArkUI 特有能力的暴露程度、调试工具的完善度,都还有不少差距。比如鸿蒙的分布式流转能力,RN 层目前就没有现成的接口;再比如深色模式、字体缩放、无障碍访问这些系统能力的细节,适配层也还没逐一对齐。
从我个人的实操经验看,如果你所在团队已经有成熟的 RN 基础设施,那么尽早把鸿蒙适配纳入规划是值得的。越晚接入,存量业务越大,适配成本越高。建议先用一个低频业务模块做试点,跑通这套流程,再逐步推广。如果是从零起步的新项目,且鸿蒙是重点平台,可以直接考虑一套代码多端复用,但要准备好在鸿蒙专有能力上做局部妥协。
最后再分享一个我差点大意失荆州的小细节:RN 适配鸿蒙时,SafeAreaView在不同设备上的表现和 Android 并不一致,尤其是带挖孔屏、圆角屏和折叠屏的设备,建议用鸿蒙原生的安全区属性做兜底。用useWindowDimensions获取的屏幕宽高和实际的 ArkUI 布局安全区域也有差异,生产环境务必用真机多机型验证一遍看起来"理所当然"的布局逻辑。