☰
React Native集成鸿蒙组件:桥接ArkUI的实践指南
2026/10/13 11:57:08 网站建设 项目流程

去年做鸿蒙应用适配时,最让我头疼的一件事就是:项目里那套用 React Native 写的界面,怎么搬到完全不再兼容 Android 的鸿蒙设备上。刚开始我以为 RN 代码可以直接跑,结果发现新一代鸿蒙系统已经不在运行时层面加载 Android 格式的安装包,RN 的底层渲染宿主直接没了。这条路走不通,就只能换方向:让 React Native 去驱动鸿蒙原生组件,组件用 ArkTS 写,渲染交给 ArkUI 引擎。这篇文章就是把这条路的关键环节拆开讲清楚,包括鸿蒙开发的基础概念、RN 与鸿蒙原生交互的桥接机制,以及一个可以直接参考的落地示例。适合正在做鸿蒙化改造的 RN 团队,也适合想把自己的 RN 技能迁移到鸿蒙生态的开发者。

1. 为什么要在 React Native 项目里集成鸿蒙组件

1.1 鸿蒙系统的运行边界已经变了

先说一个最核心的背景:鸿蒙系统已经不再把 Android 运行时当作默认兼容层。这意味着,以前那种“拿一个 Android 安装包直接装到鸿蒙设备上”的玩法,在新版本上基本行不通。RN 应用本质上是一个 Android/iOS 原生工程里的解释器加视图容器,JS 代码靠原生宿主去渲染。一旦原生的 Android View 或者 iOS UIKit 不在系统里,RN 就失去了最底层的“画板”。

但 RN 本身只是一种 JS 框架,它并不关心画板是 Android View 还是 ArkUI 组件。只要有人实现了鸿蒙系统上的渲染宿主,RN 的 JS 逻辑照样能跑,只是渲染出来的每一个视图组件,都换成了鸿蒙自己的 ArkUI 组件。这就意味着,RN 页面要能在鸿蒙设备上显示,必须做一层原生桥接,把 RN 框架里的组件请求翻译成 ArkUI 组件。而我们说的“开发鸿蒙组件”,其实就是这个桥接里最关键的部分:你写一个鸿蒙原生组件,让 RN 侧能像使用普通 React 组件一样去使用它。

1.2 集成鸿蒙组件能解决的不只是界面问题

很多团队一开始会觉得,既然鸿蒙上有自己的声明式 UI,为什么不直接用 ArkTS 重写?何必绕一圈用 RN?这里有几个实际场景:

第一,存量代码与团队技能的复用。如果团队里已经有成熟的 RN 页面,完全重写 ArkTS 的成本非常高。通过桥接层,大量逻辑层代码可以继续复用,只有真正需要原生控件的地方封装成鸿蒙组件。第二,原生系统能力的使用。鸿蒙有很多系统级能力,比如分布式文件、跨端流转、原子化服务等,这些东西没法从 JS 层直接拿,必须通过原生模块暴露出来。把这类能力封装成组件或模块,RN 侧就能跟平时调用一个普通 JS 模块一样使用它。第三,性能考虑。高频刷新或复杂绘制的模块,如果用 JS 写,桥接层丢数据会非常吃力。放进鸿蒙原生组件里,由 ArkUI 直接渲染,性能会稳很多。

所以,集成鸿蒙组件这件事,本质上是一个原生能力边界问题:你在 RN 侧能用到鸿蒙系统的多少能力,取决于你封装了多少鸿蒙原生组件和模块。这也解释了为什么动手前必须先了解鸿蒙开发的基础。

2. 鸿蒙开发基础:动手前必知的几个概念

2.1 工程模型与 ArkTS 语言

鸿蒙应用工程和 Android/iOS 都不一样。跑在设备上的入口叫 Ability,通常 UIAbility 就是包含界面的能力。工程里的源代码一般放在src/main/ets目录下,组件写在components目录,页面写在pages目录。开发语言主要用 ArkTS,它基于 TypeScript 做了裁剪和扩展,比如不允许用any、不能用运行时反射那一套,代码风格更像 SwiftUI 或 Compose 的声明式写法。

最基础的自定义组件是一个用struct声明的结构体,上面用@Component装饰器标记,然后在build()方法里写 UI。举个例子:

@Component export struct Greeting { @Prop name: string = 'World' build() { Column() { Text(`Hello, ${this.name}`) .fontSize(20) } } }

这里的@Prop是组件外部传入的属性,属性变化时 UI 会自动刷新。这个思路其实和 React 的 props 很像,但 ArkTS 是原生编译的,状态更新不经过 JS 引擎,刷新路径短很多。

2.2 状态管理、属性传递与 UI 刷新

在鸿蒙组件里,状态管理是核心。你用@State声明组件内部状态,用@Prop声明外部传入的单向属性,用@Link建立双向同步。类似地还有@Provide和@Consume,用于跨层共享数据。如果配合 RN 使用,你会发现一个很重要的对应关系:RN 里通过 JS 传给原生的 props,到了鸿蒙侧就是@Prop字段;RN 侧的原生事件回调,到了鸿蒙侧就是组件内的事件回调函数。

实际调试中,我遇到过一个问题:RN 侧修改了一个 prop,但鸿蒙组件没有刷新。后来发现,是因为我在鸿蒙组件里用@State保存了 prop 的副本,而@State只在组件内部变化时触发更新,外部传入的字段变化不会被它感知。正确做法是直接用@Prop接收,或者用@Watch监听外部传入字段的变化。这一点非常容易踩坑,尤其在封装自定义组件时。

2.3 组件生命周期与页面生命周期

鸿蒙组件的生命周期和 RN 组件不完全对齐。ArkUI 里自定义组件有几个关键方法:aboutToAppear(组件即将出现在页面时)、build(渲染 UI)、aboutToDisappear(组件即将销毁时)。UIAbility 的生命周期则包括onCreate、onWindowStageCreate、onForeground、onBackground、onDestroy等。

在集成 RN 时,通常 RN 的页面会挂载到一个 UIAbility 上,鸿蒙原生组件作为页面里的子组件展示。这里要特别留意:RN 侧组件可能因为路由变化频繁卸载和重建,但鸿蒙侧 UIAbility 可能还活着,所以不能在组件生命周期里做太多“一次性注册”的操作。比如全局事件监听,最好注册在 UIAbility 级别,组件内部只在aboutToAppear里做临时绑定,在aboutToDisappear里解除绑定。

3. React Native 集成鸿蒙的技术路径与方案选型

3.1 适配层到底在适配什么

React Native 本身不是一个完整的界面框架,它把所有跨平台差异都藏在“Host”这一层。你的 JS 代码调用<View>,RN 框架会找到当前平台对应的原生 View 组件。Android 上对应的是ReactViewGroup,iOS 上对应的是UIView的封装。在鸿蒙系统上,要实现这样的能力,就必须有一个适配层,把 RN 的组件命令映射到 ArkUI 组件。

这个适配层做的事情听起来简单,但实际上非常重:JS 引擎的接入、渲染命令的分发、触摸事件的转换、模块消息的转发、线程模型的适配,全都要有。目前比较成熟的做法是,鸿蒙官方提供了一个 RN 适配框架,社区里也有基于开源鸿蒙实现的适配方案。你在项目里要做的,其实就是依赖这个适配层,然后在上面写自己的原生模块和原生组件。

3.2 NativeModule 与自定义 UI 组件的分工

RN 提供两种与原生通信的方式:原生模块(NativeModule)和自定义原生 UI 组件。我在实际项目中经常看到有人混淆这两个概念。

原生模块是一段不参与界面渲染的代码,用来暴露 API。比如你想在鸿蒙上读取某个系统参数,就可以写一个NativeModule,在 JS 里通过NativeModules.xxx调用。自定义 UI 组件则是真正渲染出一个原生视图,JS 侧通过requireNativeComponent引入后,作为 React 组件放到 JSX 里。

我们需要开发的“鸿蒙组件”,走的是第二条路:写一个 ArkUI 组件,再把它注册给 RN 适配层,让它能被 JSX 直接使用。第一条路通常用来封装系统能力,比如文件读取、蓝牙广播等,它可以被 UI 组件内部调用,但不会直接产生界面。开发鸿蒙组件时,建议把业务逻辑和 UI 表现分开:UI 组件负责渲染,NativeModule 负责能力调用。这样后续维护时,哪怕 UI 结构改了,系统能力的逻辑也不用动。

3.3 版本匹配是第一优先级

集成 RN 和鸿蒙组件时,最让人抓狂的不是代码难写,而是版本不匹配。RN 适配层往往绑定特定的 RN 版本和鸿蒙 SDK 版本。比如你项目里用的是 RN 0.72,但适配层只适配到 0.71,那就会在编译阶段报一堆接口找不到的错误。所以我一般建议先确定一个稳定的技术组合,比如固定使用某个 RN 版本、某个鸿蒙 SDK API 级别、某个适配层版本,然后锁死,不要轻易升级其中一个。

另外,鸿蒙系统本身的 API 也在持续演进。不要让项目依赖太新的 API,除非你能确保适配层已经同步支持。如果是团队新接触鸿蒙开发,可以先用 API 级别较稳定、文档更充分的版本,避免被新特性带偏。

4. 实操:从零开发一个 RN 可调用的鸿蒙组件

4.1 准备一个鸿蒙工程和 RN 工程

这里我用一个模拟项目来演示:场景是一个“数据统计卡片”,显示一个标题和一个数字,点击卡片后把点击事件传回 RN。这个例子虽然简单,但覆盖了属性传递、事件回调、自定义组件注册三个关键点。

首先准备两个工程:一个鸿蒙工程,负责实现原生组件;一个 RN 工程,负责业务逻辑。鸿蒙工程建议用官方 IDE 创建,工程模板选择 Stage 模型的空应用,SDK 选好目标 API 版本。RN 工程直接用react-native init创建,然后把 JS bundle 的构建产物集成到鸿蒙工程的资源目录里。这里要注意,鸿蒙工程其实是最后打包成安装包的宿主,所以 RN 更像是一个“构建 JS 代码的前端工程”,真正运行和打包靠鸿蒙工程。

4.2 编写鸿蒙侧的自定义组件

先写一个最普通的 ArkUI 统计卡片组件。注意,我们不直接把这个组件暴露给 RN,而是用适配层提供的基础类再做一层封装。先写基础组件:

@Component export struct StatCard { @Prop title: string = '' @Prop count: number = 0 onClick: () => void = () => {} build() { Column({ space: 8 }) { Text(this.title) .fontSize(16) .fontColor('#333333') Text(String(this.count)) .fontSize(28) .fontWeight(FontWeight.Bold) } .padding(16) .backgroundColor('#F5F5F5') .borderRadius(12) .onClick(() => { this.onClick() }) } }

接下来,为了让 RN 适配层能识别并驱动这个组件,我们要写一个桥接组件类,继承适配层的自定义组件基类。在这里,我保持组件名与你即将在 RN 侧引用的名称一致,也就是RNStatCard:

@Component export struct RNStatCard extends ComponentBase { @Prop title: string = '' @Prop count: number = 0 build() { StatCard({ title: this.title, count: this.count, onClick: () => { this.callOnClick() } }) } callOnClick() { // 通过适配层把事件发送给 RN } }

适配层基类ComponentBase暴露了一些方法,可以在属性变化时同步给 RN,也可以把原生事件发回去。具体方法名会随适配层版本略有差异,但整体流程都是固定的:把接收到的@Prop传给真正的 UI 组件,把原生事件转换成适配层的事件对象发出。

4.3 在 RN 侧注册与调用

在 RN 工程里,我们新建一个封装组件文件。传统写法是先用requireNativeComponent把原生组件注册成 React 组件,然后导出:

import { requireNativeComponent } from 'react-native'; const RNStatCard = requireNativeComponent('RNStatCard'); export default RNStatCard;

然后在页面中使用:

<RNStatCard title="今日新增用户" count={42} onClick={event => { console.log('native event:', event.nativeEvent); }} />

这个过程中,RN 适配层会自动完成几件事:把title和count转换成 ArkTS 侧可序列化的基础类型传给鸿蒙组件;组件内点击事件触发后,通过事件通道把数据传回 JS 侧;JS 侧重新渲染时,如果props发生变化,也会自动把新值同步给原生组件。

4.4 构建、打包与真机调试

集成后的调试流程和纯 RN 项目不太一样。由于真正的运行宿主是鸿蒙工程,所以你需要先在鸿蒙 IDE 里把工程编成安装包,再把 JS bundle 作为资源一起打进包里。建议调试的时候分两步走:第一步在 RN 工程里把 bundle 打出来,确认 JS 代码没有报错;第二步到鸿蒙工程里编译原生代码,用 IDE 的日志输出过滤鸿蒙适配层和自定义组件的关键日志。

有一点很重要:鸿蒙真机调试时,若 RN bundle 是动态加载的,需要保证鸿蒙应用能访问到本机的 dev server。这时要设置好 IP 和端口,同时确保鸿蒙应用有网络权限。如果懒得折腾动态加载,可以先把 bundle 编译进工程,走离线包模式,这样问题更少。

5. 常见问题与排查技巧实录

5.1 白屏与组件注册失败

集成初期最常见的问题是页面出来一块空白,可是没有报错。这类问题九成出在组件名对不上。RN 侧的requireNativeComponent('RNStatCard'),需要和鸿蒙侧注册到适配层的组件名完全一致。组件名大小写、下划线、空格都不能差。另一个隐蔽问题是,组件类名和注册名不一致,你有两个文件,一个叫RNStatCard,一个叫StatCardBridge,注册的时候写错了一个,JS 侧却还在用老名字,最后白屏。

排查方法很简单:打开鸿蒙 IDE 的日志过滤,搜component或者UIManager相关关键字,它能告诉你某个组件名是否被找到。找不到的话,优先检查注册文件里导出的组件名。

5.2 类型映射和数据边界

RN 的 JS 数据类型和 ArkTS 的严格类型之间有一条线。基础类型number、string、boolean能直接映射,但对象、数组也有一定的序列化限制。函数不能作为属性直接传进去,因为 ArkTS 侧没有直接从 JS 拿函数的通道。如果你需要回调,一定要用事件机制,而不是试图把闭包作为 prop 传过去。

还有一个常见问题是精度丢失。JS 的Number是双精度浮点,但 ArkTS 里如果用number还好,如果原生侧是int或者long,大数字在转换时可能溢出。处理金额、ID 这类数据时,最好用字符串传递,不要用数值类型。我踩过这个坑之后,就把所有超过安全整数范围的参数全部改成 string,虽然难看了点,但再也没丢过数据。

5.3 事件回调丢失或延迟

事件从鸿蒙原生传到 RN 侧,走的是 EventEmitter 通道。很多人在aboutToAppear里注册监听事件,在aboutToDisappear里忘记移除,结果导致事件被重复监听或者收到错乱的数据。比如卡片组件内部有定时器,每次重新进入页面就多一个定时器,事件就发重复了。

更好的做法是:鸿蒙组件只负责一次性定义事件回调,RN 侧通过调用更新 props 来触发逻辑,而不是自己在原生侧维护全局监听。如果确实需要广播式监听,建议把监听器放到 UIAbility 或者一个全局的单例模块里,组件销毁时不要注销它,而是让监听器中判断当前组件实例是否仍然存活。

5.4 生命周期不同步

RN 侧组件可能会在 JS 层更新时重建,而鸿蒙原生组件可能还持有上一次的状态。最典型的情况:你在 RN 里用同一个组件名渲染多个不同业务卡片,滚出屏幕后重新滚回来,发现原生组件展示的还是上一次的数据。原因是原生组件的@Prop没有正确同步,或者说新 props 被其他生命周期函数覆盖了。

解决办法是,在鸿蒙侧自定义组件中增加一个@Watch方法去监听关键 prop 的变化,每次数据更新都直接刷新 UI 数据源。另外,尽量避免在原生组件的构造函数里保存 props 的快照,要始终从最新的@Prop取数据。

5.5 性能表现与 JS 线程压力

RN 和原生组件混搭时,性能瓶颈往往出现在 JS 线程到原生线程的事件传输。如果每次数据变化都从 JS 侧发一个事件,让原生组件刷新,高频交互下会有明显卡顿。我后来把高频变化的计数器放进鸿蒙原生组件内部管理,内部通过@State更新 UI,只有最终结果才回传给 RN。这样 JS 线程的压力小了很多,交互也跟手了。

另外,要留意 ArkUI 里的布局单位是 vp,RN 默认单位是 dp 或 px。如果尺寸在跨端传递时没有换算,组件会看起来偏大或偏小。这个细节我在示例里没有展开,但实际项目中一定要处理。

6. 工程落地时的几点额外建议

到这里,核心操作已经讲完了。最后想以个人经验的角度再多说几句,因为代码能跑通只是第一步,真正放到团队项目里还有不少工程化的问题。

第一个建议是,尽量让鸿蒙组件保持“薄”。也就是说,原生组件只做 UI 展示和简单的交互事件转发,业务逻辑尽量留在 RN 的 JS 层。这样做的好处是,后续如果 UI 需要调整,你可以不碰原生代码;如果鸿蒙 SDK 升级导致接口变化,改动面也不会很大。组件薄了,测试成本也会明显降低。

第二个建议是,组件协议要提前定好。数据模型、事件名、错误码、回调格式,都要在 RN 和鸿蒙两端同时维护一份文档。我在实际项目里吃过亏:原生侧事件名叫onCardClick,RN 侧监听的是onPress,结果调试了很久才发现是命名不一致。这种问题靠编译查不出来,只能靠运行时日志慢慢排查。

第三个建议是,权限与安全要区分清楚。鸿蒙系统在权限管理上有自己的一套逻辑,你在原生组件里访问联系人、位置等敏感权限时,需要在使用前检查权限并请求授权。RN 侧不能直接替原生请求权限,必须先通过原生模块暴露权限检查接口。考虑到权限弹窗对用户体验影响很大,建议把权限触发时机设计得比普通页面逻辑更谨慎一些。

最后分享一个调试小技巧:在鸿蒙 IDE 里同时打开 RN 的 Chrome 调试器和 ArkUI 的 UI Inspector。RN 侧报 JS 错误时,先在浏览器端的调试器里看堆栈;UI 布局或属性同步问题时,再用 UI Inspector 看 ArkUI 的实时组件树。两个窗口对比着看,很多跨端问题一眼就能定位。如果你也要走这条集成路线,希望这些经验能帮你少踩几个坑。

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

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

立即咨询