☰
React Native 集成鸿蒙组件:三条打通路径与工程实践
2026/10/14 2:46:08 网站建设 项目流程

我在做跨平台方案选型时,最常被问到的一个问题就是:React Native 这套东西,到底能不能跟鸿蒙 OS 玩到一起去?以前大家普遍觉得 RN 就是 iOS 和 Android 的事,跟鸿蒙生态没什么关系。但项目一深挖,你会发现鸿蒙组件、鸿蒙应用集成、分布式能力调用这些需求早就被摆到桌面上来了。这篇文章就是基于我个人在 React Native 工程里集成鸿蒙组件的实际踩坑经历,写清楚鸿蒙开发你需要补哪些基础,以及 RN 和鸿蒙到底怎么在工程层面真正打通。适合那些手头有 RN 项目、又希望把能力扩展到鸿蒙设备上的开发者,也适合刚接触鸿蒙、想搞清楚它和跨平台框架关系的新手。

1. 项目概述:React Native 和鸿蒙组件之间到底是什么关系

1.1 这个需求到底要解决什么

先说结论:所谓“在 React Native 中开发鸿蒙组件”,本质上有两种完全不同的诉求。第一种是想把现有的 RN 应用直接跑到鸿蒙设备上,那你要解决的是“RN 运行时怎么在鸿蒙系统里跑起来”的问题。第二种是想在你现有的 RN 工程里,调用鸿蒙系统特有的原生能力——比如分布式文件、跨设备流转、原子化服务这类 Android 和 iOS 上没有的东西——那你要解决的是“怎么把鸿蒙原生组件桥接给 RN”的问题。

这两个方向听起来相似,实际工程路径差别很大。我见过的项目里,大部分人的真实需求其实是第二种:团队里已经有一个成熟的 RN 客户端,不想因为鸿蒙单独维护一套原生代码,但又必须让应用能使用鸿蒙的特色能力。这种场景下,你把鸿蒙原生功能封装成一个可以被 RN 侧 JavaScript 直接调用的组件或模块,是最务实的做法。

而第一种需求,也就是让 RN 应用完整运行在鸿蒙上,则会涉及到更多底层适配。当前社区里已经有基于 OpenHarmony 的 React Native 适配方案,工程结构、打包脚本、JS 引擎加载方式都跟 Android 版本不一样。我建议你在动手前先想清楚:你到底是要“鸿蒙支持 RN”,还是要“RN 调用鸿蒙”。想清楚这个,后面所有的技术选型才有判断依据。

1.2 鸿蒙开发基础知识的最小集合

不管走哪个方向,鸿蒙开发的一些底层概念你是绕不开的。不需要你变成鸿蒙专家,但至少这几个名词得门儿清。

ArkTS 是鸿蒙的声明式开发语言。它是 TypeScript 的超集,语法上跟 TS 高度相似,但多了很多 ArkUI 的场景化 API。你在写鸿蒙原生组件的时候,界面部分基本都是 ArkTS + ArkUI 声明式语法。

ArkUI 是鸿蒙的 UI 开发框架,类似 Flutter 的 widget 树或者 SwiftUI 的 view 描述。它用@Component和@Builder这类装饰器来组织组件层级,页面逻辑用@State、@Prop、@Link管理状态。你如果写过 Vue 或 React,上手 ArkUI 会非常快。

接下来是 Ability。鸿蒙的应用不是按 Activity 或 ViewController 来组织的,而是按 Ability 来组织。一个 UIAbility 基本上就是一个带界面的入口,可以理解成 Android 的 Activity。RN 要跑在鸿蒙上,本质上就是要在一个 UIAbility 里挂载一个 RN 视图。

然后是一个非常重要的概念:HAP。鸿蒙的应用安装包格式叫.hap,大部分情况下一个 HAP 就是一个可以独立安装部署的应用单元。RN 项目集成鸿蒙组件之后,最终交付的产物就是一个 HAP 包,跑在鸿蒙设备上。

最后一个你必须理解的概念是分布式能力。鸿蒙 OS 的看家本领不是单设备上的 UI 效率,而是它从系统底层就支持的跨设备能力——设备发现、跨端流转、数据共享。这一块在 Android 和 iOS 上都需要借助云端或第三方 SDK 才能实现,在鸿蒙上是系统级的 API。RN 要调用鸿蒙分布式能力,最直接的方式就是把分布式 API 封装成原生模块,暴露给 JavaScript。

2. 关键技术选型:三条打通的路径,哪条适合你

2.1 路径 A:用 RNOH 框架让 React Native 应用跑在鸿蒙上

RNOH 是社区推动的 React Native on OpenHarmony 适配方案。它的目标是把 RN 的运行时、渲染管线、原生组件映射全部移植到鸿蒙系统上。

这条路径下,RN 的 JS 代码和 React 组件树基本不需要改。你的 RN 项目会通过脚手架生成一个鸿蒙工程目录,里面包含 ArkUI 写的原生宿主页面。RN 的 JS Bundle 会被加载到 Hermes 引擎里执行,UI 渲染通过 RN 的 Fabric 渲染器映射到 ArkUI 的组件上。

我做选型时比较关注的是:RNOH 对 RN 版本的跟进速度。RN 的版本迭代很快,RNOH 往往需要一定时间才能同步适配。如果你项目里的 RN 版本比较新,或者用了很多第三方原生模块,能不能顺利跑通取决于 RNOH 社区的适配进度。

这条路径适合的目标是:不想维护两套业务代码,想让 RN 项目直接覆盖鸿蒙设备。但你要做好心理准备,React Native 里那些依赖 Android/iOS 原生模块的第三方库,在鸿蒙上大概率是不能直接用的,需要你逐步替换或自己写鸿蒙适配。

2.2 路径 B:把鸿蒙原生组件封装成 RN 的 Native Module

这是我最推荐的方案,也是我实际用在生产环境里的方案。原理非常简单:RN 本身就有成熟的桥接机制,JavaScript 可以通过NativeModules调用原生代码。在鸿蒙这边,你只需要用 ArkTS 写一个原生模块,通过 RNOH 的TurboModule注册机制挂到 RN 运行时上,JS 侧就能像调用普通 JS 方法一样调用鸿蒙 API 了。

举个例子。你想让 RN 页面触发一个鸿蒙的跨设备流转,Android 和 iOS 都没有这个 API,只有鸿蒙有。你就在鸿蒙工程里写一个DistributeModule,里面调用鸿蒙的分布式能力 API,然后注册成 RN 的原生模块。RN 侧只需这样:

import { NativeModules } from 'react-native'; const { DistributeModule } = NativeModules; await DistributeModule.startDistributedShare('com.example.target.device');

这种封装方式的优点是隔离清晰。你的 RN 业务代码不用关心鸿蒙原生实现是什么样子,只需要定义一个统一的接口。鸿蒙团队和 RN 团队可以并行开发,各自测试,最后通过桥接协议对接。

适合的目标是:现有 RN 工程已经稳定运行,你只想逐步引入鸿蒙原生能力,而且不希望把整个 RN 运行时的适配风险引入到核心业务里。

2.3 路径 C:在鸿蒙原生应用里嵌入 RN 视图

这个方案的方向是反过来的。它不是为了把 RN 应用搬到鸿蒙,而是你本身在开发一个鸿蒙原生应用,但某个页面或模块想用 RN 来写——通常是团队里前端工程师多、原生工程师少,或者某个页面迭代频率极高,用 RN 可以快速发版。

实现上,你需要在鸿蒙工程的 UIAbility 里创建一个容器,把 RN 的ReactRootView挂载进去。RN 的 JS Bundle 可以本地加载,也可以远程下发。这个方案的工程复杂度比路径 B 高一些,因为你要同时维护鸿蒙原生工程和 RN 工程,还要处理两个运行时之间的生命周期同步。

我一般不建议把整个鸿蒙应用都用 RN 来写。更合理的方式是:鸿蒙原生负责框架层和系统能力调用,RN 负责那些频繁变化的业务页面。两边通过事件总线通信。这样既保留了鸿蒙的原生体验,又利用了 RN 的开发和发版效率。

2.4 三条路径怎么选

我把三条路径的适用场景整理成一张表,方便你自己对号入座。

路径适用场景工程改造成本风险点
路径 A(RNOH)新项目启动,希望一套 RN 代码覆盖所有平台中等,需要适配第三方库RN 版本升级受限于 RNOH 适配进度
路径 B(Native Module)已有 RN 工程,需要调用鸿蒙独有能力较低,桥接层清晰需要熟悉鸿蒙 API 封装
路径 C(嵌入 RN 视图)鸿蒙原生应用中的部分页面用 RN 开发较高,双工程维护生命周期和内存管理复杂

我自己的项目选的是路径 B。原因是业务侧 RN 已经沉淀了大量代码,不可能因为鸿蒙重写。而鸿蒙那侧的分布式能力只有原生能做到,所以最好的方式是两边留一个干净的接口。

3. 环境准备与工程搭建实操

3.1 工具链准备:这一步卡住了很多人

先说我踩过的第一个坑:版本不匹配。鸿蒙开发工具链和 RN 工具链各自独立迭代,你在安装的时候一定要看清楚对应关系。我这里给一个我实际验证过的组合参考:

  • Node.js:建议 18 LTS 以上,RN 的脚手架和 Metro 打包都依赖它
  • JDK:建议 17 版本。DevEco Studio 自带的 JDK 可能跟 RN 的 Gradle 配置冲突,最好在系统环境变量里统一指定
  • DevEco Studio:建议使用官网最新的稳定版,鸿蒙 SDK 也一并下载
  • React Native CLI:如果你的 RN 工程是旧版本,升级前先看一下 RNOH 插件支持的 RN 版本区间

工具链安装完以后,还要确认鸿蒙 SDK 的command-line-tools能不能在终端里正常执行。很多集成脚本会调用这些工具做签名和打包,如果环境变量没配好,后面构建的时候会报一些莫名其妙的错误。

3.2 创建 React Native 工程和鸿蒙工程

这个阶段我建议你先初始化一个全新的 RN 工程做验证,别一上来就拿生产项目试。流程是:先用 RN 官方脚手架创建出基础工程,然后用 RNOH 提供的脚手架插件在同一个工程目录下生成鸿蒙侧工程。

以我当时的操作步骤为例:

npx @react-native-community/cli@latest init HarmonyRNProject cd HarmonyRNProject npx rnoh init

rnoh init会生成一个harmony目录,里面是完整的鸿蒙工程结构,包括entry模块、module.json5、ArkTS 源码目录等。生成完成后,用 DevEco Studio 打开这个harmony目录,IDE 会自动同步鸿蒙工程配置。

这里有个容易踩的坑:rnoh init生成的鸿蒙工程默认依赖的 SDK 版本可能跟本机不一致。打开工程后先看build-profile.json5里的compileSdkVersion和targetSdkVersion,改成你本机 DevEco Studio 实际安装的版本。不改的话,构建直接报错。

3.3 配置鸿蒙工程,把 RN 运行时接入系统入口

RNOH 生成的鸿蒙工程里,入口是一个 UIAbility,它负责创建并加载 RN 的视图。你要检查的核心文件是entry/src/main/ets/entryability/EntryAbility.kt——不对,鸿蒙用的是 ArkTS,所以是.ets。这个文件里通常会创建一个ReactHost实例,然后通过它加载 RN 的 Bundle。

你需要在module.json5里确认权限配置。RN 应用跑起来以后可能访问网络、读写本地文件,这些权限都需要在鸿蒙的配置文件里申请。跟 Android 的AndroidManifest类似,鸿蒙的权限在module.json5的requestPermissions字段里声明。我踩过的坑是:只加了网络权限,结果 Metro 开发服务器能连上,但真机上应用内请求资源一直失败,后来才发现是权限没写全。

3.4 编写第一个鸿蒙原生组件:以桥接分布式能力为例

现在到重点了。假设我们要写一个鸿蒙原生模块,暴露两个方法:一个是获取当前设备的设备 ID,另一个是触发跨设备流转。

先在鸿蒙工程的ets目录下创建一个原生模块类:

import { TurboModule } from 'react-native-oh-turbo'; export default class DeviceInfoModule extends TurboModule { getDeviceId(): string { // 调用鸿蒙系统 API 获取设备唯一标识 return deviceInfo.getDeviceId(); } startDistributedShare(targetDeviceId: string): Promise<void> { return new Promise<void>((resolve, reject) => { // 在这里调用鸿蒙分布式流转 API // 成功后 resolve(),失败则 reject() }); } }

然后在模块注册文件里把这个类注册成 RN 可识别的原生模块:

import { TurboModuleRegistry } from 'react-native-oh-turbo'; TurboModuleRegistry.registerModule('DeviceInfoModule', DeviceInfoModule);

RN 侧调用就非常简单了:

import { NativeModules } from 'react-native'; const DeviceInfo = NativeModules.DeviceInfoModule; const deviceId = DeviceInfo.getDeviceId(); await DeviceInfo.startDistributedShare(selectedDeviceId);

这里面有个非常重要的细节:鸿蒙的分布式能力 API 大部分是异步回调或 Promise 风格的,你在封装 Native Module 的时候一定要把异步逻辑处理好。RN 侧拿到的应该是一个返回 Promise 的接口方法,而不是一个塞回调函数进去的 C 风格 API。这样封装出来的模块对 RN 业务层面最友好。

3.5 编译 HAP 包并在模拟器或真机上调试

模块写完之后,就可以在 DevEco Studio 里执行构建了。构建产物是一个.hap包,可以直接安装到鸿蒙模拟器或真机。

调试阶段建议先用模拟器跑通 JS 到原生模块的基础调用链路,再上真机测试分布式相关功能。因为分布式能力在模拟器上往往会有很多限制,比如没法真实模拟多设备发现。你需要在 DevEco Studio 的远程模拟器里手动添加多个虚拟设备来测试流转流程,但效果还是不如两台真机来得直接。

真机调试调试分布式能力,注意两台设备要登录同一个账号,并且在设置里开启多设备协同。否则设备发现阶段就会卡住,而不是你的代码有问题。

4. 核心实现细节:桥接原理与组件映射

4.1 RN 与鸿蒙的桥接机制到底是怎么工作的

很多朋友问过我:RN 和鸿蒙之间的通信,跟 RN 和 Android/iOS 之间有什么本质区别?其实核心机制是一脉相承的,RN 把 JavaScript 侧的调用序列化以后,通过桥接层分发到原生模块,原生模块执行完再把结果回传。

鸿蒙这边不同的一点在于,RNOH 走的路线更接近新一代的 TurboModule 架构,而不是老的 NativeModule 架构。也就是说,它是通过 JSI(JavaScript Interface)直接建立 JS 和原生侧的调用关系,省去了老架构里 JSON 序列化和异步消息队列的损耗。

这意味着你在封装鸿蒙模块的时候,要注意方法签名必须符合 JSI 的约束。比如只支持可序列化的参数类型,复杂对象要转成字典或字符串,不能像早期 RN 一样传一个跨语言的实例引用。这条我从实践里得来的经验能帮你少走不少弯路。

4.2 原生组件是如何映射到自动化 UI 渲染的

RN 的 UI 组件在鸿蒙上不能直接渲染,需要经过一层映射。RN 的View在渲染时,UI Manager 会向原生侧请求对应的原生视图组件。在 Android 上是ReactViewGroup,在 iOS 上是RCTView,而在鸿蒙上,RNOH 提供了一套映射关系,把 RN 的View、Text、ScrollView这些基础组件映射到 ArkUI 的对应组件上。

这不是简单的一对一翻译。ArkUI 的组件模型和 RN 的组件模型在布局、事件、样式属性方面都各有特点。RNOH 要做的事情是把 RN 的 Yoga 布局结果转换成 ArkUI 的布局参数,把 RN 的合成事件转换成 ArkUI 的 gesture 事件。

所以如果你要写一个自定义的鸿蒙原生组件(比如把某个 ArkUI 特有的复杂组件暴露给 RN 使用),你得同时处理两层:一层是 ArkUI 组件的 UI 展示逻辑,另一层是它跟 RN 的事件和布局对接逻辑。

4.3 鸿蒙独有能力的暴露思路:以分布式数据为例

鸿蒙最强的就是分布式能力,这也是它有别于 Android 和 iOS 的最大差异点。你要在 RN 里用上分布式数据管理,思路是把它封装成 RN 的原生模块。

鸿蒙的分布式数据服务允许不同设备上的应用读写同一个数据副本,数据会通过系统底层同步。你在封装的时候,可以考虑暴露这几个接口:初始化一个分布式数据库、写入键值对、监听远端数据变化。RN 侧拿到的是一个统一的数据访问层,但背后实际使用的是鸿蒙特有的分布式能力。

这里有一个产品层面的建议:不要试图把鸿蒙所有的分布式能力都一口气暴露给 RN,那样会让桥接层变得非常臃肿。先挑业务上最需要的两三个能力做模块化封装,跑通整个链路,再逐步扩展。我见过一个团队一次性封装了十几个分布式接口,结果半年过去了,实际被业务调用的只有两个。

5. 常见问题与排查经验

5.1 构建失败:绝大多数是版本和配置问题

我先说一个最典型的报错:Gradle 或构建系统提示 SDK 版本找不到。这一步在鸿蒙 SDK 更新之后尤其常见。RNOH 生成的工程里默认的compileSdkVersion可能指向某个 IDE 自带的 SDK 版本,而你本机装的是另一个。

排查的顺序我建议这样走:先看 DevEco Studio 里 SDK Manager 里的安装版本,再去看build-profile.json5和local.properties里的 SDK 路径配置,最后确认环境变量里的命令行工具路径没有被某个旧的缓存覆盖。

另一个我去踩过的高频问题是:JDK 版本冲突。RN 工程本身依赖的 Gradle 和鸿蒙工程的 Gradle 对 JDK 版本要求不同,如果你的系统默认 JDK 是最新版,很可能在某个环节报“Unsupported class file major version”。解决办法是给鸿蒙工程单独指定一个 JDK 版本,不改全局环境。

5.2 桥接调用失败:JS 侧拿不到模块

TypeError: null is not an object (evaluating 'NativeModules.DeviceInfoModule')

这个报错出现的时候,首先别急着去查 JS 代码。大概率是原生模块没有正确注册。排查路径是:先确认原生模块类有没有在注册文件里被导入,然后确认模块注册的时机是不是在 RN 运行时启动之前。

我处理过一个极端情况:模块注册代码写在了鸿蒙的某个页面生命周期方法里,RN 视图加载需要依赖这个模块,结果模块还没有注册,JS 侧就执行了调用。解决办法是把注册逻辑提到 Ability 的onCreate阶段,保证它先于 JS Bundle 执行。

5.3 真机调试的签名和开发证书问题

很多人在模拟器上跑得好好的,一上真机就闪退或装不上。这时候先别怀疑代码,大概率是签名和证书的配置问题。鸿蒙应用上真机运行需要配置签名证书,DevEco Studio 支持自动签名,但如果你用了自定义证书,记得检查 profile 文件的描述符是否跟应用包名匹配。

真机调试分布式功能时还有一个容易被忽略的点:两台设备必须处于同一个局域网,且都开启了多设备协同。我见过有人排查了很久代码,最后发现是一台设备没有开启协同开关。

5.4 性能优化:渲染卡顿与 JS 线程瓶颈

最后说性能问题。RN 在鸿蒙上的性能瓶颈,跟它在其他平台上的性能瓶颈很像:JS 线程和 UI 线程之间的通信频率过高会导致卡顿。

如果你发现某个页面滚动掉帧严重,优先检查是不是在滚动手势里频繁调用了原生模块。我的一般优化思路是:用节流或批处理把这些高频调用合并成一次原生通信,而不是让每个事件都走一次桥接。另外,尽量减少跨线程的 JSON 序列化,尤其是大数据量传递的时候。

6. 一点实操体会

这篇文章写到这里,基本上把 RN 和鸿蒙组件集成这件事从前到后都过了一遍。我在实操中觉得最有价值的一个经验是:别一开始就追求一套方案管到底。HF鸿蒙生态和 RN 生态都在快速演进,你今天搞定的适配方案,可能半年后就被官方的新能力或者新框架取代。比较稳妥的做法是,把两种生态之间的边界当成一个可以随时替换的适配层来设计,只要两边接口稳定,底层方案换代不会影响你的业务代码。

最后想单独建议的是:如果你手头的团队里既有 RN 开发者又有鸿蒙开发者,一定要让两边尽早开始结对开发,不要各写各的然后留到最后联调。RN 和鸿蒙这对组合的知识跨度非常大,越早让对方理解各自的边界,后面集成阶段就越顺利。我在这个项目里最深刻的体会就是,技术本身并不难,难的是让两个不同技术背景的人在同一套接口上达成共识。把这个共识定好,你的跨平台鸿蒙之路就已经成功了七八成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询