前阵子接到一个项目,要求把一部分已有业务从 Android 侧平滑迁到鸿蒙(HarmonyOS)上,同时还要兼顾团队现有的 React Native 技术栈。调研了一圈后发现,直接在鸿蒙上用 ArkTS 重写全部业务成本太高,于是我们走了“React Native 渲染 + 鸿蒙原生组件下沉”的混合路线。这条路踩了不少坑,但也把关键技术链路摸清楚了。这篇就专门聊聊:在 React Native 项目里开发鸿蒙组件,到底该怎么落地。
不管你是刚开始接触鸿蒙的前端同学,还是已经在 DevEco Studio 里写过几个 Demo 的客户端开发,“RN 怎么调鸿蒙原生能力”“鸿蒙的 har/hsp/hap 到底怎么和 RN 工程配合”这些话题,应该都能从我这里找到一些可参考的答案。我会尽量把环境搭建、工程配置、组件封装、打包发布到问题排查整个链路都讲透,确保看完了能动手。
1. 先想清楚:React Native 和鸿蒙是怎么“兼容”到一起的
1.1 核心认知:是“适配层”,不是“重新实现”
很多人第一次听说 React Native 能开发鸿蒙组件时,第一反应是“RN 不是只能跑在 Android 和 iOS 上吗?”确实,RN 官方目前没有正式支持鸿蒙,但社区早就有了一套非常成熟的适配方案——react-native-harmony,它由 OpenHarmony SIG 持续维护,核心思路不是给鸿蒙写一套新的 JS 引擎,而是在鸿蒙系统上增加一个 RN 运行时层,让 JS 代码最终渲染到鸿蒙的 ArkUI 组件体系上。
打个比方:RN 在 Android 上负责把 JSX 映射成 Android View,在 iOS 上映射成 UIView,而在鸿蒙这里,它映射成 ArkUI 的组件。用户看到的界面是鸿蒙原生的,摸起来的手感也是鸿蒙原生的,但业务逻辑、页面路由、状态管理全都跑在 JS 层。这就意味着团队里现有 RN 代码不用推翻重写,只需要在需要深度系统能力的地方,通过“原生模块 + 原生组件”的方式,把鸿蒙的能力暴露给 JS 调用。
这里一定要区分两个概念:
- React Native 应用包:通过 RN 框架打包出来的 JS Bundle 和鸿蒙原生壳工程合成的 App。
- 鸿蒙原生组件/模块:用 ArkTS 写的、运行在鸿蒙系统上的原生能力单元,RN 侧通过桥接层调用。
在实际工程里,它们是两个既独立又耦合的部分。理解这个关系后,后面所有环境配置和代码组织就都不绕了。
1.2 为什么选择“RN 壳 + 鸿蒙原生组件”混合方案
项目选型时通常有三种思路:纯 ArkTS 重写、RN 全量迁移、RN + 鸿蒙原生混合。纯 ArkTS 重写在业务量小且团队熟悉鸿蒙时最干净,但大多数团队的问题是业务逻辑集中在 JS 层,重写成本太高。RN 全量迁移呢,看起来省事,但实际上总会撞上一些必须走系统 API 的场景,比如推送、扫码、安全存储、硬件通信,这些在 RN 生态里没有现成库里只能自己写原生。
最终的折中方案就是“RN 主业务 + 鸿蒙原生组件下沉”:页面尽量在 RN 层写,碰到没法用通用库解决的系统能力,就用鸿蒙 ArkTS 封装成原生模块;如果某个页面交互特别复杂、强依赖系统控件和手势,那就直接用鸿蒙原生页面承载,通过路由参数和 RN 页面互相跳转。这个方案的最大好处是渐进式迁移,一次只迁移一个业务模块,哪怕上线后发现某个模块有问题,也可以随时切回 RN 版本,不至于伤筋动骨。
从团队角度来看,这个方案对人员技能要求也友好得多:前端工程师继续写 JS/TypeScript,客户端工程师只需要把精力放在原生模块封装和性能优化上,两边不用互相等。
1.3 技术链路全景:从 JS 到 ArkUI 的一次完整旅程
日常开发时,一次最简单的“RN 页面点击按钮 -> 调用鸿蒙原生能力 -> 返回结果渲染到界面”,底层链路其实是这样的:
- RN 侧 JS 代码通过
NativeModules或 TurboModule 发起调用。 - 调用进入 react-native-harmony 的适配层,适配层把调用路由到对应的 ArkTS 原生模块。
- ArkTS 原生模块执行真正的系统逻辑(比如读取剪贴板、初始化扫码、调用蓝牙)。
- 执行结果通过 Promise 或 Callback 原路返回 JS 层。
- JS 层拿到数据后 setState,触发 UI 更新,最终刷新 ArkUI 渲染树。
链路本身不复杂,但每一步都有值得注意的细节。比如第 2 步的 TurboModule 注册方式、第 3 步的装饰器使用、第 5 步的状态同步,都会直接决定功能能不能跑通、性能好不好。后面我会逐一拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境与工程搭建:HarmonyOS + RN 双端联动
2.1 开发环境清单与版本匹配
这部分直接给你一套我验证过多次的组合,照着装基本不会出问题:
| 软件 | 推荐版本 | 说明 |
|---|---|---|
| Node.js | 18 LTS 或 20 LTS | RN 构建和 Metro 服务必需,老项目注意看自家 package.json 要求 |
| JDK | 17 | DevEco Studio 和 Gradle 构建链需要,别用 11 或 21 代替,会有兼容性问题 |
| DevEco Studio | 5.0.x 及以上 | 鸿蒙官方 IDE,创建工程和打包 hap 都靠它 |
| HarmonyOS SDK | API 12 及以上 | 对应 DevEco 内置 SDK,项目编译时需要 |
| react-native | 0.72 或 0.73 | 根据 react-native-harmony 的版本适配表选择,不要盲目上新版 |
| react-native-harmony | 0.72.x 或 0.73.x | 要和 RN 大版本严格对应,错一个版本都跑不起来 |
版本匹配是第一个大坑。react-native-harmony 的发布准则是“跟随 RN 版本”,比如 react-native-harmony@0.72.x 就对应 RN 0.72 系列。我见过不少同学直接 npm install react-native-harmony@latest,结果和本地的 RN 版本对不上,编译时报一堆 .so 找不到的错误。建议初始化工程前,先去 GitHub 的 react-native-harmony Releases 页面看版本矩阵,确定组合后再动手。
2.2 创建 RN 工程并接入 HarmonyOS
假设现在从零开始,初始化一个全新的 RN + 鸿蒙工程:
bash复制# 1. 使用 RN 官方脚手架创建工程
npx @react-native-community/cli@latest init RNHarmonyDemo
# 2. 进入工程并安装鸿蒙适配层
cd RNHarmonyDemo
npm install react-native-harmony
# 3. 执行鸿蒙工程初始化脚本
npx react-native-harmony-setup
这个 react-native-harmony-setup 脚本会自动在工程根目录生成一个 harmony 文件夹,里面就是鸿蒙壳工程。用 DevEco Studio 把这个 harmony 目录打开就能看到和标准鸿蒙工程几乎一致的结构,区别在于多了一些 RN 相关的依赖和配置。
如果不想用自动脚本,也可以手动创建鸿蒙工程再引入 RN SDK,但我建议第一次还是用自动脚本,因为手动配置 build-profile.json5 里的 reactNativeVersion 时很容易写错版本号,一旦写错,编译时各种莫名报错会让人怀疑人生。
2.3 关键配置文件详解:build-profile.json5 和 module.json5
打开 harmony 目录里的 build-profile.json5,你会看到类似这样的配置:
json复制{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true
}
}
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
重点看两个地方:一是 compatibleSdkVersion,它决定了你用哪个版本的 HarmonyOS SDK 来编译;二是模块列表,entry 是应用的主入口模块。RN 鸿蒙壳工程一般还会多一个 rn 模块,这个模块专门放 RN 的运行时资源,包括 JS Bundle 的加载逻辑。
再看 entry/src/main/module.json5,这里有一个特别关键的配置——网络权限。RN 页面在开发阶段需要从 Metro 加载 JS Bundle,如果没开网络权限,轻则白屏,重则直接闪退:
json复制{
"module": {
"name": "entry",
"type": "entry",
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
这个 INTERNET 权限在 module.json5 的 requestPermissions 里声明,漏掉的话,真机调试时 RN 页面必然白屏。我在项目里至少见过三次这种情况,每次都是团队新同学在配置时手滑删了这一项。
2.4 初始化完成后的目录结构认知
执行完初始化,harmony 目录大概长这样:
text复制harmony
├── AppScope
├── entry
│ └── src
│ └── main
│ ├── ets
│ │ ├── entryability
│ │ └── pages
│ ├── resources
│ └── module.json5
├── build-profile.json5
├── hvigorfile.ts
└── oh-package.json5
entryability 是鸿蒙应用的生命周期入口,RN 的启动逻辑通常就在这里被触发。pages 目录下会有一个默认的 ArkUI 页面,这个页面加载 RN 的根组件视图。实际开发时,你可以把 pages 里的页面当作 RN 的“宿主页面”,鸿蒙原生的跳转入口也可以在这里添加。
3. 鸿蒙组件的核心开发套路:装饰器、原生模块与桥接
3.1 必须掌握的 ArkTS 装饰器
鸿蒙开发里“装饰器”和前端那种装饰器不是一回事。ArkTS 里的装饰器更像一种声明式语法的标记,告诉编译器“这个类是一个组件”“这个变量要响应式刷新”。在 RN 鸿蒙开发场景下,最常打交道的装饰器有下面几个:
@Entry:标记页面入口组件,一个页面文件里只能有一个。@Component:标记一个自定义组件,可以理解为 ArkUI 里的“React 组件”。@State:标记组件内部的状态变量,数据变了 UI 自动刷新,类似 React 的useState。@Prop:父组件传过来的单向数据,子组件改不了。@Link:父子组件共享的双向数据,类似 React 的受控 props 加强版。@Watch:监听某个状态变量的变化,类似 Vue 的 watch。@NativeModule:这是 react-native-harmony 提供的关键装饰器,用于标记一个类为 RN 可调用的原生模块。
其中 @NativeModule 和 @State 是日常开发里最核心的两个。前者负责“桥”,后者负责“界面”。
3.2 实战:封装一个返回系统信息的鸿蒙原生模块
先从一个最简单的例子入手,我封装一个 DeviceInfoModule,让 RN 侧能读取鸿蒙设备的型号和系统版本。
鸿蒙侧的实现:
typescript复制import { NativeModule, TurboModule } from 'react-native-harmony';
import { deviceInfo } from '@kit.BasicServicesKit';
@NativeModule('DeviceInfoModule')
export class DeviceInfoModule extends TurboModule {
getDeviceModel(): string {
return deviceInfo.deviceModel;
}
getSystemVersion(): string {
return deviceInfo.displayVersion;
}
}
注意这里类名 DeviceInfoModule 和 @NativeModule 括号里的字符串要一致,RN 侧就是通过这个名字来定位原生模块的。extends TurboModule 是必须的,react-native-harmony 会检查这个基类。
RN 侧调用:
javascript复制import { NativeModules } from 'react-native';
const DeviceInfoModule = NativeModules.DeviceInfoModule;
const model = DeviceInfoModule.getDeviceModel();
const version = DeviceInfoModule.getSystemVersion();
console.log('设备型号:', model, '系统版本:', version);
就这么简单,一个可以同步返回结果的桥接模块就通了。这里有几个细节:
- 如果原生模块的方法不需要异步返回结果,可以直接同步返回;但如果有耗时操作(比如读取文件、请求网络),千万别同步返回,要返回 Promise。
- RN 侧拿到的模块对象是
TurboModule的代理,调用方法和普通 JS 对象没区别,但内部是跨语言调用,参数类型有限制,尽量只传简单类型(string、number、boolean)和可 JSON 序列化的对象。
3.3 带 Promise 的异步原生模块
实际业务里,同步返回结果太少了,更多是异步操作。比如我要封装一个“扫描局域网设备”的模块,鸿蒙侧得等扫描完成才能回传结果。
鸿蒙侧:
typescript复制import { NativeModule, TurboModule } from 'react-native-harmony';
@NativeModule('LanScannerModule')
export class LanScannerModule extends TurboModule {
scanDevices(timeoutMs: number): Promise<string[]> {
return new Promise((resolve, reject) => {
// 这里写真正的局域网扫描逻辑
setTimeout(() => {
resolve(['device-a', 'device-b']);
}, timeoutMs);
});
}
}
RN 侧:
javascript复制import { NativeModules } from 'react-native';
const LanScannerModule = NativeModules.LanScannerModule;
LanScannerModule.scanDevices(5000)
.then((devices) => {
console.log('扫描结果:', devices);
})
.catch((error) => {
console.error('扫描失败:', error);
});
要注意的是,Promise 机制在桥接层是通过回调实现的,如果你在鸿蒙侧用了 async/await,返回的 Promise 对象会被 react-native-harmony 自动拆成 resolve/reject 回调,RN 侧感觉不出来,但鸿蒙侧的函数签名必须显式标注返回 Promise<T> 类型。漏掉类型标注,编译能过但运行时会拿不到结果,这是比较容易踩的暗坑。
3.4 桥接自定义 UI 组件:把 ArkUI 视图嵌入 RN 页面
除了调用原生能力,更常见的需求是把鸿蒙的原生 UI 组件直接嵌到 RN 页面里。比如鸿蒙有一个特色组件是侧边抽屉(SideBarContainer),RN 生态里很难完全复刻它的交互,这时候就可以桥接过去。
鸿蒙侧定义一个原生组件类:
typescript复制import { Component, ViewBase, Prop } from 'react-native-harmony';
@Component
export class SideBarContainer extends ViewBase {
private sidebarWidth: number = 200;
private isOpen: boolean = false;
@Prop
onSidebarStateChange: (state: boolean) => void = () => {};
render() {
return (
// 这里用 ArkUI 的组件描述结构
// SideBarContainer 是 ArkUI 系统组件
);
}
}
RN 侧注册并使用这个组件:
javascript复制import { requireNativeComponent } from 'react-native';
const SideBarContainer = requireNativeComponent('SideBarContainer');
function HomePage() {
return (
<View style={{ flex: 1 }}>
<SideBarContainer
style={{ flex: 1 }}
onSidebarStateChange={(e) => console.log('侧边栏状态:', e.nativeEvent)}
/>
</View>
);
}
桥接 UI 组件比桥接模块要复杂的地方在于事件处理。RN 侧通过 props 传入的回调,在鸿蒙侧需要用 @Prop 标记并手工触发;鸿蒙侧往 RN 侧传数据时,要通过事件对象把参数包进去。这块很容易因为事件名对不上导致 RN 侧收不到通知,排查时建议先在鸿蒙侧打日志确认事件确实发出来了,再去 RN 侧看监听有没有绑定成功。
3.5 常见装饰器问题速查
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 页面显示空白,控制台无报错 | @Entry 装饰器漏了或存在多个 |
确认页面文件里只有一个组件打了 @Entry |
| 修改变量后 UI 不刷新 | 普通变量没有加 @State 或 @Prop |
检查组件头部的装饰器声明 |
| 子组件改值,父组件没反应 | 用的是 @Prop 而不是 @Link |
双向数据场景改用 @Link 或回调 |
@NativeModule 类方法不能调用 |
类没有继承 TurboModule |
补上 extends TurboModule |
RN 侧 NativeModules.xxx 为 undefined |
原生模块没有被注册到工程 | 检查模块文件是否被正确 import 进入口文件 |
4. 打包与发布:搞懂 hap、hsp、har 三者的关系
4.1 三种包类型到底怎么选
热词里有一条是“可以打包成 hap、hsp、har 的鸿蒙 demo”,这确实是鸿蒙工程里绕不开的概念。简单理解:
- HAP(HarmonyOS Ability Package):应用安装包,直接装到设备上的东西。
- HAR(HarmonyOS Archive):静态共享包,类似 Android 的 AAR,编译时把代码和资源一起打进去,用它的模块最后会变大。
- HSP(HarmonyOS Shared Package):动态共享包,类似 Android 的 so 动态库,运行时加载,多个 HAP 可以共用一份代码,包体积优化效果明显。
放到 RN + 鸿蒙的场景里,它们的关系就非常清晰了:你的最终应用是一个 HAP,里面包含了 RN 运行时和业务 Bundle;如果你想把一些鸿蒙原生组件能力开放给团队内其他模块复用,可以先打成一个 HAR 或 HSP,不同的 RN 页面模块按需引用。特别是当 App 里有多个 HAP(鸿蒙支持多 HAP 特性),把它们公共的 RN 运行时抽到 HSP,能显著减少重复代码。
4.2 RN 鸿蒙工程的打包流程实操
在 DevEco Studio 里,打一个上线用的 HAP 包流程如下:
- 先配置签名:打开
File -> Project Structure -> Signing Configs,勾选自动签名(需要登录华为账号),或者手动导入 .p12 和 .cer 证书。 - 在 RN 工程根目录执行
npm run bundle,生成index.android.bundle或自定义名称的 JS Bundle,这一步必须有,不然打出来的 HAP 里没有业务代码。 - 把生成的 Bundle 放进鸿蒙工程的
resources/rawfile目录,并在鸿蒙入口代码里指定BundleName和BundlePath。 - 在 DevEco Studio 里点击
Build -> Build Hap(s)/APP(s),选择Build Hap(s)。 - 产物在
entry/build/default/outputs/hap目录下,就是带signature.hap后缀的安装包。
这个过程有几个关键细节:
- JS Bundle 的名称和路径必须和鸿蒙侧入口代码里的配置一致,默认是
index,对应文件名是index.bundle。改错名字,产物打出来能装上,但一启动就是白屏。 - 签名配置缺失时,DevEco 会直接报错“Install failed due to invalid signature”,这时候去检查签名配置,不要试图绕过去。
- 如果用了代码混淆,RN 侧和鸿蒙侧的类名映射要一起测,混淆配置不当很容易出现运行时找不到原生模块的问题。
4.3 HAR 打包实践:把鸿蒙组件能力沉淀给团队
当我们的原生组件越写越多,最好的做法是抽到一个独立的 HAR 模块里,这样 RN 工程主模块和其他鸿蒙原生模块都能引用。
在 DevEco Studio 里新建一个 har 模块(File -> New -> Module -> Static Library),然后把所有 @NativeModule 类放到这个模块里,主模块通过 oh-package.json5 的 dependencies 引用它。
HAR 模块的目录结构:
text复制harmony
├── library
│ ├── index.ts
│ ├── oh-package.json5
│ └── src
│ └── main
│ ├── ets
│ │ └── DeviceInfoModule.ets
│ └── module.json5
└── entry
编译后,主模块里直接使用 HAR 里导出的类就可以了。团队内共享时,直接把 HAR 上传到私有仓库,其他人改 oh-package.json5 里的版本号就能升级。这个方式比每次把源码复制到主工程干净太多,也避免了模块间命名冲突。
4.4 HSP 动态包的适用场景
HSP 在 RN 鸿蒙工程里的典型场景是:一个大的 RN 业务模块需要按需加载,不希望打进主 HAP 增加启动体积。比如“高级设置页”是一个单独的 HSP,用户点进去时才从设备上动态加载。
实现上和 HAR 类似,但注意两点:HSP 有自己的生命周期,加载失败时要做兜底 UI;HSP 之间通信必须通过后台提供的接口,不能直接 class 引用。对于新手团队,我不建议一开始就上 HSP,等业务真正大到需要时分包再搞,不迟。
5. 问题排查与性能调优:那些文档里不会写的实战心得
5.1 启动白屏:第一杀手,原因有三个
热词里“react native 启动白屏”高居不下,因为这在 RN 鸿蒙开发里实在太常见了。我统计了一下自己项目里的排查路径,90% 的白屏都能归到下面三个原因:
- JS Bundle 没加载成功:开发模式下 Metro 没启动、宿主页配置的 Bundle URL 不对、真机设备和电脑不在同一个局域网。排查方法很简单:看鸿蒙侧启动日志有没有报
load bundle failed,或者直接开 DevEco 的日志窗口,过滤RN关键字。 - 缺少 INTERNET 权限:前面提过,
module.json5里漏了网络权限,开发模式下必白屏。生产模式下如果 Bundle 打包进 HAP 里倒是不受影响,但建议始终保留这个权限。 - RN 版本和 react-native-harmony 版本不匹配:编译能过,运行时机型相关的问题会在这时候集体爆发。
针对白屏问题,我强烈建议在入口页面加一个“加载超时检测”。比如鸿蒙侧启动 RN 根组件后设置一个 10 秒的定时器,如果 JS 侧没有回调表示就绪,就在原生侧渲染一个错误界面,带上错误码。这个做法能帮你快速区分是“加载慢”还是“加载失败”,不至于每次都要跨端打断点排查。
5.2 调试连不上:DevEco 和 Metro 的调试链路配置
RN 开发时,react-native start 启动 Metro 后,App 需要主动找 Metro 拉 Bundle。Android 上通常用 adb reverse 或者直接配 localhost:8081,鸿蒙这边情况不太一样。
鸿蒙真机调试时,Metro 地址要填电脑在局域网里的 IP,不能填 localhost。这个地址在初始化脚本生成的配置里一般是自动填好的,但如果你换过网络环境(比如从公司 WiFi 换到家里),IP 变了就必须手动改。
具体位置通常在鸿蒙工程入口代码里有一个 BundleUrlProvider 或类似配置,写死了一个 MetroHost 常量,把它改成你的电脑当前 IP:
javascript复制// 鸿蒙侧 RN 启动配置文件
export const METRO_HOST = '192.168.1.100:8081';
另外,鸿蒙模拟器访问宿主机地址时,localhost 指向的是模拟器自己,需要改用模拟器提供的宿主机映射地址,这点和 Android 模拟器的 10.0.2.2 很类似。
5.3 性能优化:状态刷新频率控制和异步操作管理
鸿蒙原生组件嵌进 RN 页面后,性能瓶颈通常不是渲染本身,而是跨语言调用的频率。举个例子,如果 JS 侧在一个 requestAnimationFrame 循环里每秒 60 次去读鸿蒙原生模块的某个属性,每一次都穿越一次引用边界,性能直接拉垮。
优化手段有几个:
- 尽量减少
@State变量的更新频率,把多次小更新合并成一次大更新。 - 能用事件回调解决的问题,不要用轮询。鸿蒙侧的主动事件推送能力比 Android 那套方便,要利用起来。
- 原生模块方法里不要做阻塞操作,必须异步。鸿蒙侧的耗时操作会阻塞 UI 线程,RN 主线程也会跟着卡顿。
另外,RN 页面如果包含大量图片,建议不要直接走 RN 的 Image 去加载鸿蒙文件系统里的资源,先在原生侧写一个图片加载模块,把图片转成 base64 或者通过自定义组件渲染,性能差别非常大。这块我在一次聊天页面改造里实测过,走原生组件的加载速度比纯 JS 侧快 3 倍以上。
5.4 常见问题快查表
| 问题 | 排查方向 |
|---|---|
| 鸿蒙原生模块 RN 侧总是 undefined | 检查 @NativeModule 字符串和 RN 侧调用名是否一致;检查模块文件是否被入口文件 import |
| 原生组件在 RN 页面只显示空白 | 检查组件是否继承对了基类;检查 render() 方法里 ArkUI 组件描述结构是否完整 |
| Metro 连接正常但页面刷新慢 | 降低状态更新频率,合并 setState 调用,检查是否在逻辑里有自热循环 |
| HAP 安装到真机失败 | 检查签名配置;检查 module.json5 里 deviceTypes 是否包含当前设备类型 |
| ohpm install 依赖下载失败 | 检查网络环境,可以切换国内镜像仓库(DevEco 内置了镜像源管理) |
5.5 除了纯技术,还要防几个“人的坑”
这一条更像团队协作层面的提醒。RN 鸿蒙混合开发里,最常见的延期原因往往不是技术难点,而是两端协议没对齐。RN 侧同学认为“原生模块的返回字段肯定是 camelCase”,鸿蒙侧同学实际返回了 snake_case;RN 侧认为错误信息应该在 Error 对象里,鸿蒙侧却把错误码塞进了返回值。
最好的做法是在工程一开始就定一份 TypeScript 接口定义(.d.ts),把每个原生模块的方法签名、参数、返回值、错误码全部列清楚。鸿蒙侧按这个接口实现,RN 侧按这个接口调用,后面联调基本是顺手的事。我们项目后期甚至把这份接口定义直接生成了 ArkTS 的 interface 文件,两边共用一套,彻底告别“他说他没传,我说我不可能没传”的扯皮。
6. 写在最后的一些个人体会
技术方案再好,最终还是要落地到团队协作和工程质量上。我个人这几轮项目下来最大的体会是:RN 开发鸿蒙组件这件事,难度不在于某个单一技术点,而在于跨端调试的耐心和细节的把控。同样的功能,在 Android 上跑通可能只需要半小时,到鸿蒙上可能要折腾一下午,因为要多查一层桥接日志、多确认一次装饰器有没有漏写。
不过往长远看,鸿蒙生态已经是大势所趋,尽早让团队具备 RN + 鸿蒙的混合开发能力,后面再扩业务时会从容很多。如果你正准备从零开始搭建,我的建议是:先不要追求把所有业务都迁过来,选一个功能完整的模块(比如登录页或设置页)先跑通全流程,等团队对配套工具链都熟了,再逐步扩大范围。毕竟工程化的问题,做得越早,后面省的事就越多。
