尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

React Native集成鸿蒙全流程:手把手实现自定义鸿组件

React Native集成鸿蒙全流程:手把手实现自定义鸿组件 做跨端开发的人最近应该都在关注同一个话题React Native 怎么跑在鸿蒙HarmonyOS上尤其是业务方要求把现有的 RN 代码搬进鸿蒙设备或者反过来要在 RN 工程里嵌入鸿蒙原生的复杂能力时“鸿组件”这个词就躲不掉了。我踩了不少坑今天把整个集成链路拆开讲清楚。这篇内容适合三类人一是准备把现有 RN 项目往鸿蒙平台迁移的团队二是想在 RN 项目里复用鸿蒙原生能力的前端开发者三是刚接触鸿蒙、想搞明白 RN 和鸿蒙到底怎么协作的同学。我会从鸿蒙开发的基础概念讲起再到 RN 与鸿蒙集成的具体方案最后给出一套可以直接参考的自定义鸿组件实现流程。先提个醒鸿蒙开发和传统的 Android 开发不是完全一回事但也没到要重新学一遍操作系统的程度。只要把 Ability、ArkUI、ArkTS 这几个核心概念梳理清楚再配合 DevEco Studio 把工程跑起来后面的事情基本就顺了。1. 鸿蒙开发基础先搞清楚这块地基再上手1.1 鸿蒙到底是什么和安卓有什么本质区别很多人以为鸿蒙只是换了个皮的安卓这是最大的误解。鸿蒙是一套面向全场景的分布式操作系统它最核心的设计目标是让手机、平板、车机、智能家居等设备之间能够无缝协同。从应用开发者的视角来看鸿蒙提供了自己的应用框架、UI 框架和编译器工具链开发语言也默认推荐 ArkTS而不是 Java 或 Kotlin。这个底层差异导致了一个很直接的结果安卓的.apk装不到鸿蒙上基于安卓写的原生 Module 也不能直接复用。所以在 React Native 里开发鸿蒙组件我们不能把 Android 的 ViewManager 代码直接搬过来而是需要用鸿蒙的原生语法重新实现一遍再把这一层作为 RN 的桥接暴露给 JavaScript。不过也不用太慌RN 到鸿蒙的适配方案现在已经比较成熟社区里已经有专门维护的底层库我们只需要按照约定写鸿蒙侧的 ViewManager / Module剩下的大部分通信逻辑由适配层处理。1.2 必须提前掌握的四个鸿蒙概念我在开发过程中最常被问到的问题就是我没有系统学过鸿蒙怎么开始写第一个鸿蒙组件这里划四个重点也是后面所有实操的基础。第一个是 Ability。它是鸿蒙应用的基本运行单元类似于安卓的 Activity。一个应用可以有多个 Ability比如主界面、后台服务都可以是 Ability。在 RN 接入鸿蒙的场景里通常会有一个主 UIAbility 负责承载整个应用生命周期RN 的页面可以作为一个单独的页面组件挂在这个 Ability 上。第二个是 ArkUI。这是鸿蒙的声明式 UI 框架语法习惯跟 Flutter 有点像也有点像 SwiftUI。你用它来描述界面长什么样而不是一步一步去操作控件。比如你要一个按钮就声明一个Button给它传属性而不是像传统 Android 那样findViewById再设置监听。第三个是 ArkTS。这是鸿蒙主推的开发语言基于 TypeScript 做了扩展。如果你的团队已经有 TypeScript 基础上手 ArkTS 会非常快。一个典型的 ArkTS 文件里会有一个Component装饰的自定义组件然后在build()方法里写 UI 结构。第四个是 DevEco Studio。这是鸿蒙的官方 IDE基于 IntelliJ IDEA 定制。你不仅要用它创建鸿蒙工程、编译生成 HAP 包还要用它连接鸿蒙设备或模拟器进行调试。RN 集成鸿蒙之后Metro 负责 JS 打包DevEco Studio 负责原生工程编译两个工具是并行的。把这些概念先过一遍再往工程里加 RN就不会觉得每一步都像在盲人摸象。1.3 DevEco Studio 工程结构每个目录是干什么的一个标准的鸿蒙工程会分成entry、common、libs等模块但初期只需要关注几个关键目录entry/src/main/ets/下面主要放应用代码其中entryability是应用入口pages是页面components是自定义组件。开发 RN 鸿组件的时候我们会在这里新增原生组件文件。entry/src/main/resources/放资源文件比如字符串、颜色、图片。entry/src/main/module.json5是模块配置权限声明、Ability 注册都在这里后面排查白屏或没权限的时候经常要看这个文件。build-profile.json5和hvigorfile.ts是编译和脚本配置RN 接入时需要确认依赖是否正确挂上。我个人的建议是不要跳过这一步直接去查 RN 集成文档。先自己用 DevEco Studio 跑通一个 Hello World 的鸿蒙应用熟悉一下点击运行、查看日志、部署到模拟器的流程。因为后面一旦出现白屏或者崩溃你要同时面对 RN 和鸿蒙两套框架的问题如果没有这个基本功排查起来会非常痛苦。2. React Native 集成鸿蒙选对路径少走一半弯路2.1 当前主流的两条集成路线我在调研阶段发现 RN 和鸿蒙的集成方案可以粗略分两类。第一类是非官方适配方案目前社区里维护得最多的是react-native-harmony。这个库由 OpenHarmony 社区的相关 SIG 组和一批开发者共同维护目标是把 React Native 的渲染层映射到 ArkUI 上让 RN 的组件最终通过鸿蒙的原生控件绘制出来。它的版本更新比较快基本能跟着 RN 的版本走所以在实际项目里应用很广。第二类是官方合作方案近几年华为和 OpenHarmony 社区也在推动官方适配包括一些系统级组件和 API 的兼容。这类方案的优势是更稳定但大部分能力还在逐步补齐很多第三方 RN 库的鸿蒙适配仍然得靠社区贡献。选择哪条路取决于你的业务现状。如果项目已经停留在 RN 0.6x 或 0.7x 版本太久建议先升级到社区适配主版本如果还没有上线可以直接用最新的稳定版本用react-native-harmony模板初始化工程这样省去很多手动配置的麻烦。2.2 react-native-harmony 和 OpenHarmony 的定位很多新手会被 HarmonyOS 和 OpenHarmony 这两个名字搞晕。简单理解OpenHarmony 是开源底座HarmonyOS 是商业化产品。RN 适配层主要基于 OpenHarmony 的能力实现所以你在很多文档里会看到react-native for OpenHarmony的说法。实际上在 DevEco Studio 里创建工程时选 SDK 版本的时候就要注意RN 适配要求特定版本的 API 支持不能随便选。以我目前用的版本为例RN 0.72 react-native-harmony0.72.x搭配 DevEco Studio 4.x 和 API 10 以上整体是比较稳的组合。版本不匹配会出现编译错误比如找不到某个符号、ArkUI 组件 API 不存在这类问题非常折磨人。2.3 初始化工程从 RN 脚手架到鸿蒙端能跑起来下面是我们项目实际走过的一次初始化流程步骤不算复杂但每一步都不能省略。先创建一个普通 React Native 项目npx react-native0.72.10 init RNHarmonyDemo cd RNHarmonyDemo然后安装react-native-harmony相关的依赖和脚本。用 npm 安装npm install react-native-harmony接下来需要在项目目录下运行适配脚本生成鸿蒙工程目录。社区模板通常提供类似npx rnoh --init的命令这个脚本会自动生成一个harmony文件夹里面就是一个完整的 DevEco Studio 工程。之后用 DevEco Studio 打开harmony文件夹等待工程同步完成后在 DevEco Studio 里配置签名证书然后选择设备运行。这里有一个非常关键的检查项MainApplication 要能正确加载 ReactNativeHost并且要配置好 Metro 的 bundle 入口。如果你看到 HarmonyOS 设备上出现了 React Native 的启动画面说明整条链路已经通了。如果直接白屏或者闪退就进入后面第 4 章的排查环节。2.4 集成时的工程配置和依赖管理实操我习惯把配置文件的检查顺序固定下来每次集成新机器或同事拉分支后都能按这个流程排错。第一查看harmony工程根目录下的oh-package.json5确认是否依赖了react-native-oh/react-native-harmony等核心库版本号要和 package.json 里的 RN 版本对应。第二打开entry模块的module.json5检查有没有配置网络权限。因为 Metro 开发模式下真机需要通过局域网访问电脑上的打包服务如果没有ohos.permission.INTERNET大概率会导致 JS bundle 加载失败。第三检查entry/src/main/ets/entryability/EntryAbility.kt或.ets文件确认loadFromUrl()或者loadBundle()指向的地址是否正确。模拟器可以用10.0.2.2代替localhost真机则要填电脑的局域网 IP。第四在 DevEco Studio 的Run Configuration里确认签名配置已经选好否则无法安装到真机。这套流程我写在了团队文档里遇到白屏问题先走一遍能解决掉至少一半的问题。3. 手写一个鸿蒙原生组件并在 React Native 里调用3.1 什么时候需要自己写鸿组件RN 官方社区已经带了很多内置组件比如 View、Text、ScrollView 等。但业务需求往往更刁钻有时候需要读取 NFC 标签有时候需要唤起鸿蒙的原生扫码界面有时候要做一个性能要求很高的图形绘制组件。这些能力在 JS 层和普通 RN 组件上实现不了就需要在鸿蒙侧写一个原生组件再通过 RN 的桥接层暴露给前端。在 RN 里这类原生组件分两种一种是纯 UI 组件对应安卓的 ViewManager在鸿蒙里对应 ArkUI 的Component另一种是功能模块对应安卓的 Native Module在鸿蒙里实现一个TurboModule。我这里重点说 UI 组件因为它是“鸿组件”这个说法最直接的体现。3.2 在 ArkTS 中声明一个可被 RN 使用的组件假设我们要写一个带圆角阴影的卡片组件在 ArkUI 里就是这个样子Component export struct ShadowCard { Prop title: string Prop shadowColor: string #000000 Prop cornerRadius: number 12 build() { Column() { Text(this.title) .fontSize(16) .fontColor(#333333) } .padding(16) .backgroundColor(#FFFFFF) .borderRadius(this.cornerRadius) .shadow({ radius: 8, color: this.shadowColor, offsetX: 0, offsetY: 4 }) } }这个ShadowCard如果放在普通鸿蒙工程里就是一个标准的自定义组件。但在 React Native 里它还不能直接被 JS 使用因为 RN 不知道它长什么样、接收哪些属性。我们需要给它包装一层原生桥接通过 ViewManager 告诉 RN“我有一个原生视图名字叫 RNTShadowCard属性有 title、shadowColor 和 cornerRadius。”3.3 编写 ViewManager把 ArkUI 组件暴露给 RN在鸿蒙侧的 RN 适配框架里实现一个自定义 ViewManager 的代码大致是这样的export class ShadowCardViewManager extends SimpleViewManagerShadowCard { override getName(): string { return RNTShadowCard } override createViewInstance(reactContext: Context): ShadowCard { return new ShadowCard() } PropSetter public setTitle(view: ShadowCard, value: string) { view.title value } PropSetter public setShadowColor(view: ShadowCard, value: string) { view.shadowColor value } PropSetter public setCornerRadius(view: ShadowCard, value: number) { view.cornerRadius value } }注意这里的PropSetter只是我按社区常见风格写的示例不同版本和框架的装饰器名称可能不同。你在实际项目里以react-native-harmony对应版本的 API 文档为准。核心思路是一样的注册一个组件名定义创建方法再用属性设置器把 JS 传过来的 props 同步到 ArkUI 组件上。写完之后还需要把这个 ViewManager 注册到 Package 里。一般会写一个ShadowCardPackage然后在入口模块的 PackageProvider 列表里加上它。3.4 在 JS 层调用自定义鸿组件原生侧包装完成后前端 JS 侧的使用和普通自定义组件类似。先用requireNativeComponent注册import { requireNativeComponent } from react-native; const RNTShadowCard requireNativeComponent(RNTShadowCard); export default function ShadowCard(props) { return ( RNTShadowCard title{props.title} shadowColor{props.shadowColor} cornerRadius{props.cornerRadius} style{props.style} / ); }这样在业务代码里就能像使用普通 RN 组件一样使用ShadowCard了。如果属性类型多变还可以配合codegenNativeComponent生成类型声明让 TypeScript 也具备完整智能提示。这里我想强调一个细节原生组件的属性命名风格建议统一采用 camelCase。虽然 RN 在大多数情况下会自动转换但自定义属性如果不小心用了带-的属性名桥接层可能解析失败这是我在项目里踩过的坑。3.5 JS 与鸿蒙侧的双向通信除了把属性从 JS 传给原生我们经常还要接收原生侧的事件比如按钮点击、卡片滑动、组件生命周期回调。RN 处理原生事件的机制是原生侧通过DirectEvent方式向 JS 发送事件。在鸿蒙组件里可以通过类似this.events.emit(topOnCardClick, { data: xxx })的方式触发事件。JS 侧在组件上绑定onCardClick即可RNTShadowCard title点击卡片 onCardClick{(event) { console.log(native event:, event.nativeEvent); }} /如果你以前写过 Android 的 React Native 原生模块会发现这套逻辑非常眼熟。区别只是把 Kotlin/Java 换成了 ArkTS把 Android 控件换成了 ArkUI 组件。所以如果你已经有了 Android 原生 RN 开发经验转向鸿蒙组件开发不会太痛苦关键是要适应 ArkUI 的声明式写法和新 IDE 的工作流。3.6 用 TurboModule 暴露非 UI 能力除了 UI 组件业务里还会经常需要调用非 UI 的原生能力比如读取设备序列号、调用系统剪贴板、获取电池状态。这些在 RN 里对应 Native Module在鸿蒙侧的适配方案里通常叫 TurboModule。我简单描述一下注册流程。先在 ArkTS 侧写一个DeviceInfoModule实现TurboModule接口里面定义getBatteryLevel(): Promisenumber这样的方法。然后在 Package 里注册这个 ModuleJS 侧就可以通过接口拿到原生暴露的方法对象import { NativeModules } from react-native; const { DeviceInfoModule } NativeModules; const level await DeviceInfoModule.getBatteryLevel();TurboModule 的优势在于类型安全和低开销的通信但接入门槛比普通 ViewManager 高一些需要同时维护原生接口定义和 JS 规范代码。如果你的项目里只有一两个原生能力直接用 NativeModules 也能解决如果模块多建议老老实实按 TurboModule 规范来。4. 集成和调试中的老问题白屏、加载失败、日志看不到4.1 启动白屏首先查 Metro 和 Bundle 地址凡是 RN 上鸿蒙的项目遇到最多的问题就是“应用启动了但页面空白”。我见过不下十次这种情况最后定位到的原因基本集中在三个地方。第一个是 Metro 没启动或者 bundle 地址错误。React Native 开发模式要求 Metro 打包服务持续运行鸿蒙端应用启动时会主动从 Metro 拉取 JS bundle。如果真机通过 WLAN 访问电脑地址往往要填http://192.168.x.x:8081/index.bundle?...千万不要写localhost因为鸿蒙设备上的 localhost 指向设备自己。第二个是 DevEco Studio 工程里的网络权限没开。如果module.json5里没有ohos.permission.INTERNETMetro 请求直接失败页面自然白屏。第三个是 RN 核心库版本和鸿蒙适配库版本不匹配导致 JS 层某个模块加载异常。这个问题最隐蔽因为原生编译可能不会报错但运行时却无法初始化。排查方法就一条检查package.json和oh-package.json5里的版本号是否严格对应。4.2 原生组件加载不出来先检查注册和包名当你按照第 3 章实现了一个自定义鸿组件JS 侧却始终报“Invariant Violation: requireNativeComponent: RNTShadowCard was not found”。这个报错说明原生侧没有正确注册。我的排查顺序是固定的确认 ViewManager 的getName()返回的字符串和 JS 侧requireNativeComponent传入的组件名完全一致区分大小写。确认在PackageProvider里把ShadowCardPackage加进了列表且主入口模块没有被缓存。重新编译部署应用把 DevEco Studio 里的 App 卸载干净避免老代码残留。打开 Metr 日志如果能看到原生包注册信息再查看是否有自定义组件加载痕迹。还有一个容易忽略的点某些鸿蒙版本对自定义组件的包名规范有要求比如不能包含特殊字符不能以数字开头。我遇到过因为组件名带了下划线直接加载失败的情况改成驼峰之后就正常了。4.3 DevEco Studio 和 Metro 双端日志怎么看很多人会把 DevEco Studio 和 Metro 的输出混在一起导致问题定位很慢。我建议明确职责Metro 日志主要看 JS 层报错比如TypeError、模块解析失败、bundle 加载超时。这类日志通常会在终端窗口显示红色的报错堆栈。DevEco Studio 的HiLog窗口主要看原生层崩溃和系统级警告比如reason: get error、ArkTS TypeError、Napi调用失败等。调试时先在 JS 代码里加console.log然后在 Metro 终端观察输出同时可以在鸿蒙侧关键位置加console.info用 DevEco Studio 的日志过滤关键字。如果两侧日志都看不到我们想要的内容再考虑是不是日志开关被关掉了有些 release 包默认不输出 console 信息开发模式要确保isDebug为 true。4.4 热更新和调试下的性能陷阱最后提醒一个性能踩坑点很多人刚集成的时候喜欢把所有页面和逻辑都在 RN 层实现结果渲染性能在低端鸿蒙设备上特别糟糕。原因往往是 ArkUI 组件与 RN 组件之间频繁的通信比如在ScrollView中大量使用自定义原生组件并且每个组件都在 props 变化时触发原生属性 setter。优化方向有三个减少不必要渲染把跨端通信的数据合并传输以及将高频更新的功能尽量下沉到鸿蒙原生侧。如果某个功能交互很复杂比如地图、视频播放建议直接用鸿蒙原生页面作为独立模块而不是强行用 RN 实现。5. 从零到一的项目集成清单和个人体会如果你现在准备在团队里正式启动 RN 鸿蒙的项目我建议拿着下面这份清单一步步对照可以省掉很多沟通成本确认鸿蒙设备系统和 DevEco Studio 版本记下 SDK API Level初始化 RN 项目固定 RN 主版本和react-native-harmony适配版本用脚本生成鸿蒙工程并确保在模拟器上成功运行 HelloWorld配置网络权限、签名证书、正确 bundle 地址集成核心第三方库时先在文档里查鸿蒙兼容情况从最简单的跨端事件跑通双端通信再写第一个自定义鸿组件验证 ViewManager 注册链路最后再考虑复杂业务模块和性能优化我个人实际操作中体会最深的一点是RN 和鸿蒙的集成真正难的不是写代码而是版本矩阵的匹配。React Native 迭代快鸿蒙适配库迭代也快两边版本只要错开一个版本可能就会遇到难以排查的底层问题。所以最好的策略是把 RN 版本钉死在某个稳定版本不要频繁升级同时把鸿蒙适配库也锁定版本。等团队完全稳定后再做统一升级规划。最后再分享一个小技巧在开发阶段建议把 Metro 的端口固定下来并写成脚本一键启动。鸿蒙工程这边把签名配置写进环境变量避免每次拉新代码都要重新配置。这些小细节会极大提升团队协作效率。这个方向后续还可以扩展比如在鸿蒙平板上适配大屏布局、把 RN 和鸿蒙共享组件库沉淀成公司内部 npm 包、研究深度优化跨端通信性能等。希望这篇内容能帮你把第一条路走通。
返回列表