1. 项目概述
1.1 先聊聊为什么我会做这个项目
做React Native开发的朋友应该都有体会,跨端框架最怕的就是“换了个系统就歇菜”。我手头有一批RN的存量业务代码,一直是跑在Android和iOS上的,但最近团队开始接触OpenHarmony生态,手里的鸿蒙设备(rk3568开发板、rk3588开发板)陆续到位,第一个念头就是:能不能把RN这层复用过去?毕竟业务逻辑、组件树、状态管理这些都是现成的,要是能直接在OpenHarmony上跑起来,节省的可不止是几个月的时间。
于是就有了这个TodoList项目。选TodoList作为首个验证项目,原因很朴素:它麻雀虽小但五脏俱全——有列表渲染、有状态管理、有交互事件、有样式系统,还涉及到原生模块的调用(比如后面提到的rn调用电话功能)。如果TodoList能跑通,那基本可以证明这条技术路线是可行的。而渐变背景色这个需求,则是很多实际业务页面都会遇到的视觉需求,正好用来验证RN的样式系统在OpenHarmony上的兼容性。
先说结论:整体跑通之后,我觉得RN for OpenHarmony这套方案已经具备了一定的生产可用性。但中间踩过的坑也不少,从环境搭建到原生依赖编译,从样式兼容到事件处理,每一个环节都有值得记录的地方。这篇文章就是想把整个过程完整地梳理一遍,给准备在这个方向上趟路的同学一些参考。
1.2 这个项目能解决什么问题
如果你问我“RN for OpenHarmony”现在到底是什么状态,我会说:能用,但需要一点耐心。它不是一个开箱即用的成熟方案,更像是“RN的OpenHarmony适配层已经搭好了骨架,血肉还需要你根据实际场景去填充”。
具体到我这个TodoList项目,它解决的问题有三个层面。第一,验证RN核心渲染流程在OpenHarmony上的可行性——从JS层到原生层的组件树构建、样式计算、布局绘制,这一整条链路是否跑得通。第二,验证RN的生态兼容性——我用了React Navigation来管理页面路由,用了简单的状态管理,还测试了渐变背景色这种稍微复杂的样式,这些都是日常业务中的高频场景。第三,建立一套可复用的工程模板——如果后续有其他RN项目要迁移过来,可以直接拿这个工程作为起点,省去重复的环境配置和踩坑过程。
1.3 适合谁来参考
这篇文章适合两类人。第一类是正在评估“要不要把RN项目迁移到OpenHarmony”的技术决策者或架构师,你可以通过这篇文章快速了解这条路的整体难度和技术风险点。第二类是准备实际动手干活的RN开发者或鸿蒙原生开发者,你在搭建环境、配置工程、调试问题的时候,很大概率会遇到和我一样的问题,这篇文里的实操记录和排查思路可以直接帮你少走很多弯路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工程搭建
2.1 OpenHarmony开发环境的坑与配置心得
折腾OpenHarmony开发,第一步就是环境准备。我用的设备是rk3568开发板和rk3588开发板,这两块板子目前是社区里最常见的OpenHarmony硬件平台。如果你用的是rk3568,编译OpenHarmony系统镜像的时候需要特别注意版本匹配问题——不同版本的OpenHarmony对内核和驱动的要求不太一样,最好先用官方预编译镜像跑起来,再考虑自己编译。
开发环境方面,我推荐使用DevEco Studio来做OpenHarmony应用开发,它对ArkTS和OpenHarmony SDK的支持比较完善,调试工具也齐全。不过这里有个坑:DevEco Studio版本和OpenHarmony SDK版本需要严格对应,版本不匹配的话,编译时会出现各种莫名其妙的问题。我的建议是到官方网站下载最新的DevEco Studio,然后用它自带的SDK Manager下载对应版本的SDK,不要自己手动去配。
另外一个经常被问到的问题是 devudid 和 serial。在做设备调试的时候,连接rk3568开发板,需要先获取设备的UDID和序列号,用于应用签名和调试授权。获取UDID的方法是在DevEco Studio的终端里执行命令:
bash复制hdc shell bm get -u
或者通过HDC(OpenHarmony Device Connector)工具来查询:
bash复制hdc list targets
hdc shell param get const.product.name
我这里特别说一下 const.product.name 这个参数。有时候你拿到的开发板是别人改过的系统,产品名被修改过,导致DevEco Studio识别不到正确的设备类型,进而影响签名和安装。如果你的设备在DevEco Studio里一直显示“未授权”或者“不匹配”,可以先查一下这个参数是不是标准值。另外,hdcd服务必须确保在设备端正常运行,否则hdc工具是连不上设备的。
2.2 RN for OpenHarmony 的工程初始化
RN for OpenHarmony目前有一个官方维护的运行时仓库,包含了RN核心的C++层实现和OpenHarmony的适配层。初始化一个RN工程的思路,和标准的React Native CLI初始化很像,但需要额外接入OpenHarmony的原生工程。
我这里用最直白的方式说下整体结构。一个RN for OpenHarmony工程,实际上由三部分组成:
| 组成部分 | 作用 | 说明 |
|---|---|---|
| JS层 | 业务逻辑、UI组件 | 和普通RN工程完全一致,写的就是React代码 |
| RN运行时 | 桥接层、组件映射、样式计算 | 官方开源仓库提供的适配实现 |
| OpenHarmony原生壳 | 应用入口、页面容器、原生模块 | 用ArkTS/ArkUI编写,负责承载RN渲染 |
初始化的步骤大致如下。首先准备好一个标准的RN工程:
bash复制npx react-native init RNHarmonyTodo
然后拉取RN for OpenHarmony的运行时源码,把它集成到Android/iOS工程之外,再新建一个OpenHarmony的entry模块。这一步比较关键,因为需要手动配置CMakeLists和包依赖,把RN的C++核心代码编译进鸿蒙应用里。官方文档有一套标准的集成流程,我强烈建议你先照着跑一遍官方Demo,确保环境没问题之后,再开始你自己的项目。
这里有个容易踩的坑:RN的版本和OpenHarmony运行时仓库的版本必须对齐。我用的是RN 0.72版本的API,对应的OpenHarmony运行时也是要匹配0.72的分支。版本错位的话,编译期可能不报错,但运行的时候会出现组件渲染不出来或者直接闪退的诡异问题。
2.3 设备编译与安装
当工程初始化完成并成功编译出HAP包之后,就需要安装到开发板上运行了。安装方式很简单,用DevEco Studio的Run按钮可以直接部署到连接的真机上。但我更推荐在终端里用hdc命令行来装,更方便集成到自动化流程里:
bash复制hdc install entry-default-signed.hap
安装成功之后,启动应用:
bash复制hdc shell aa start -a EntryAbility -b com.example.rnharmonytodo
这里要注意,OpenHarmony应用安装到真机上是需要签名的。DevEco Studio默认会使用自动生成的调试证书,但如果你用了自定义的 const.product.name 或者换了设备,签名文件需要重新生成。调试签名出问题的时候,安装会报一个类似于“Signature verification failed”的错误,排查的时候先看签名。
3. TodoList 项目的核心设计思路
3.1 从需求到组件的拆分逻辑
TodoList的业务逻辑本身不复杂,但如果把目标定为“验证RN在OpenHarmony上的能力”,那每一个功能点都应该有针对性地设计。我给自己定了几个任务:列表要能滚动、条目要能新增和删除、状态要能被管理(完成/未完成切换)、页面样式里要包含渐变背景色。
基于这个需求,我把页面结构拆成四个部分:
- 页面容器:负责整体布局,承载背景色和渐变效果;
- 输入区:文本框加确定按钮,用于新增待办事项;
- 列表区:使用FlatList渲染待办条目,支持下拉刷新和删除;
- 状态控制:使用React Hooks管理todo数据和过滤逻辑。
从组件选型上,我特意选择了FlatList而不是ScrollView加Map的组合,因为在数据量大的时候FlatList的虚拟化机制对性能的优化非常明显。另外,FlatList在RN for OpenHarmony上的适配也比较成熟,这本身就是个测试点。
关于渐变背景色,React Native官方样式系统里其实一直都没有直接提供 linear-gradient 属性,通常的做法是用第三方库 react-native-linear-gradient,或者在StyleSheet里通过 backgroundImage 配合渐变图片来实现。在OpenHarmony这边,我测试了两种方案,后面会详细说。
3.2 为什么选择这套技术组合
在选择技术方案的时候,我权衡过几种路径。一种是直接用ArkUI从头写这个TodoList,这样最稳,因为ArkUI是OpenHarmony的“亲儿子”,性能和原生能力都没问题。但这样做的话,RN的存量代码就完全没法复用了,跨端价值归零。另一种是用 Flutter for OpenHarmony,这个方案也有人在尝试,但Flutter和RN在OpenHarmony上的成熟度半斤八两,而且团队的存量技术栈是RN,没必要换赛道。
最终我选了RN for OpenHarmony,核心逻辑有三条。第一,业务代码复用率最大化——React组件、状态管理、路由配置这些代码一行都不用改,改的只是原生壳和打包配置。第二,社区生态可以平移——React Navigation、Redux/Zustand、axios这些库都还有机会继续用,虽然有些需要验证兼容性,但至少不是从零开始。第三,学习成本最低——团队里RN开发者不需要重新学ArkTS,原生开发的同学只需要理解RN的桥接机制,就能在这套架构里玩得转。
3.3 页面交互设计:点击其他区域触发事件的实现思路
在TodoList里有个交互细节:用户点击页面的空白区域时,需要把输入框的焦点收回去(也就是键盘收起),同时取消某个条目的选中状态。这个需求正好对应热搜词里的“rn如何实现点击页面其他区域执行某个函数”。
在标准RN里,实现这个功能通常有两种方式。第一种是外层包一个 TouchableWithoutFeedback 或者 Pressable,然后设置 onPress 回调来执行对应函数。第二种是使用 Keyboard.dismiss() 来手动收起键盘。在RN for OpenHarmony上,这两种方式我都验证过,结论是:
TouchableWithoutFeedback在OpenHarmony上可以正常工作,点击空白区域会触发onPress事件;Keyboard.dismiss()方法在OpenHarmony的适配层里也能调用,但需要确保当前页面有键盘实例,否则在某些版本上会静默失败。
我的最终实现是在最外层容器上套了一个 Pressable,然后在事件处理函数里同时执行“收起键盘”和“重置选中状态”两个逻辑。这里有个细节:Pressable组件的覆盖范围必须包含整个页面区域,并且它的层级要低于列表和输入区,否则子组件会拦截点击事件。用 StyleSheet.absoluteFill 来给Pressable设置全屏样式,是一个比较稳妥的做法。
4. 渐变背景色的实现与踩坑实录
4.1 方案选型:第三方库 vs 原生适配
渐变背景色在RN标准生态里基本就是 react-native-linear-gradient 一家独大。这个库通过原生View在Android和iOS上实现了渐变效果,性能不错,API也很简单。我当时第一个想法就是把 react-native-linear-gradient 也用到OpenHarmony工程里,但很快就碰了壁:这个库根本没有OpenHarmony的原生实现,它的Android/iOS代码在OpenHarmony平台上无法编译。
这就引出了一个普遍性问题:RN for OpenHarmony的生态兼容性,取决于每一个第三方库有没有对应的OpenHarmony原生实现。像 react-native-linear-gradient 这种带原生代码的库,目前没戏;但纯JS实现的库,比如很多状态管理库、工具库,是可以直接平替过来的。
在OpenHarmony上实现渐变背景色,我当时找到了三套可行方案,并逐一做了验证:
| 方案 | 实现方式 | 性能 | 复杂度 | 效果 |
|---|---|---|---|---|
| 方案A | 在ArkUI原生侧封装LinearGradient组件,通过RN桥接暴露给JS | 高 | 高 | 最佳 |
| 方案B | 使用react-native-svg的LinearGradient绘制渐变矩形作为背景 | 中 | 中 | 良好 |
| 方案C | 使用静态渐变图片作为背景图 | 低 | 低 | 一般 |
方案A是最“正统”的做法,但需要懂ArkUI原生开发,并且要在RN上手动实现一个原生UI组件。对于只是想快速验证效果的项目来说,这个成本有点高。方案C虽然最简单,但渐变是静态的,如果背景色需要跟随主题动态变化,就完全没法用了。最终我选了方案B,用SVG来实现渐变背景,理由很简单:react-native-svg 在OpenHarmony上已经有适配版本了,而且LinearGradient是SVG标准能力,效果纯粹。
4.2 基于 react-native-svg 的渐变实现
用SVG实现全屏渐变背景的代码很直观。我把SVG放在页面的最底层,通过绝对定位撑满整个页面,然后在SVG里定义一个 LinearGradient 渐变对象,用它填充一个全屏的 Rect。
jsx复制import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
function GradientBackground() {
return (
<Svg style={StyleSheet.absoluteFill}>
<Defs>
<LinearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0%" stopColor="#4A90D9" />
<Stop offset="100%" stopColor="#7B68EE" />
</LinearGradient>
</Defs>
<Rect x="0" y="0" width="100%" height="100%" fill="url(#bgGradient)" />
</Svg>
);
}
关于颜色搭配,我这里用了一个偏蓝到紫的渐变,这是设计上比较保险的选择,适合Todo工具类应用,视觉上干净又不单调。如果你要换别的颜色组合,只需要调整 stopColor 的值就行,不需要动其他任何代码。
这个方案的优点体现在几方面。第一,它是矢量渲染,无论屏幕分辨率怎么变化,渐变都不会失真。第二,它支持多个渐变段的叠加,比如你可以在同一个渐变里加三个、四个Stop,实现更丰富的色彩过渡。第三,性能表现不错,因为它本质上只是一个原生绘图操作,不是复杂的嵌套布局。
4.3 为什么不用CSS方案:RN样式系统的边界
有些做Web开发转过来的同学可能会问:React Native不是支持样式吗?能不能直接通过样式属性实现渐变?
这里要解释清楚一个概念:RN的样式系统并不是CSS,它是一个简化的、基于Yoga布局引擎的样式子集。RN支持的颜色、尺寸、flex布局等样式属性,最终是通过原生组件映射到平台UI框架上的。在Android上映射到Android的View系统,在iOS上映射到UIKit,在OpenHarmony上则映射到ArkUI的组件属性。
问题在于,linear-gradient 这个样式属性,在标准RN的StyleSheet类型定义里根本不存在。RN原生组件里也没有对应的属性映射。所以你在StyleSheet里写 background: 'linear-gradient(...)',编译不会报错,但运行的时候这个属性会被直接忽略,背景色就是空白或者默认色。
这也是为什么渐变必须依赖原生组件或者SVG来做的深层原因——样式系统不支持的东西,就要靠原生能力兜底。
4.4 渐变性能优化与视觉效果调整
用SVG渐变背景还有个好处,就是可以通过调整渐变方向来改变视觉重心。比如TodoList的页面顶部通常要放标题栏和输入框,如果把渐变方向从“左上到右下”改成“从上到下”,并且把浅色放在顶部,深色放在底部,视觉上会更聚焦在输入操作区域。
实际调整参数的时候,我建议把 LinearGradient 的 x1、y1、x2、y2 想象成一条线的起点和终点坐标,渐变就是沿着这条线铺开的。比如:
x1="0%" y1="0%" x2="100%" y2="0%":水平渐变,左到右;x1="0%" y1="0%" x2="0%" y2="100%":垂直渐变,上到下;x1="0%" y1="0%" x2="100%" y2="100%":对角渐变,左上到右下。
性能方面,如果渐变背景是静态的,且和页面其他内容没有叠加关系,那对帧率的影响基本可以忽略。但如果你在渐变背景上再叠加一个BlurView或者半透明遮罩,那GPU的负担会明显增加,低端设备上有可能会出现掉帧。我的建议是:渐变背景放在最底层,不要让其他视图频繁重绘盖在上面,尤其是列表滚动的时候,最好用不透明背景的列表容器或者给列表项设置不透明白色背景,避免列表滚动时背景层反复触发混合计算。
5. 列表渲染与状态管理
5.1 FlatList性能与数据更新的实测
TodoList的核心是列表渲染。在RN for OpenHarmony上,FlatList的适配情况直接决定了这个框架能不能用。我实测下来,FlatList在数据量小于100条的时候,滚动性能非常流畅,和原生列表没有明显区别。当数据量超过500条时,快速滚动会出现轻微的掉帧,但考虑到TodoList场景通常不会有这么大的数据量,这个表现已经可以接受了。
FlatList在OpenHarmony上能保持性能的核心,在于它复用了RN的虚拟化列表机制——只渲染可视区域内的列表项,屏幕外的项目会被回收。这个机制在Android和iOS上是成熟的,OpenHarmony适配层同样保留了这个能力。如果你在列表里用ScrollView加Map的方式渲染,数据一多就会出现严重的卡顿,那种方式在OpenHarmony上我实测200条数据就开始掉帧了。
更新数据时要注意一个细节:FlatList的 extraData 属性必须设置。因为FlatList是PureComponent,如果不用 extraData,当列表数据变化但 data 的引用没有变(比如直接修改数组的某个元素),FlatList不会重新渲染。我在项目里用了useState管理数组,每次更新都生成新的数组引用:
jsx复制const [todos, setTodos] = useState([]);
const toggleTodo = (id) => {
setTodos(prev => prev.map(item =>
item.id === id ? { ...item, done: !item.done } : item
));
};
这里 map 会返回一个新数组,所以FlatList能感知到数据变化。
5.2 useState vs useReducer:状态管理的选择
TodoList这种小规模的状态,用useState就够了。但如果你打算把这个项目扩展成更复杂的业务应用,我建议在早期就切换到useReducer或者接入Zustand/Redux,因为TodoList的典型状态流转——新增、删除、切换完成——其实很适合用reducer来统一管理,而且后面加“筛选全部/已完成/未完成”这些功能时,状态逻辑会越来越复杂,useState会显得杂乱。
我用useReducer的实现方式是这样的:
jsx复制const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD':
return [...state, { id: Date.now(), title: action.payload, done: false }];
case 'TOGGLE':
return state.map(item =>
item.id === action.payload ? { ...item, done: !item.done } : item
);
case 'DELETE':
return state.filter(item => item.id !== action.payload);
default:
return state;
}
};
const [todos, dispatch] = useReducer(todoReducer, []);
这种结构在调试的时候优势很明显,每一个状态变化都是一个独立的action,可以直接打印、回溯。
5.3 列表项的样式与交互细节
列表项的设计上,我做了一个简单的卡片样式:圆角、阴影、左右滑动露出删除按钮(这个用到了RN的Swipeable组件,但OpenHarmony上的适配还不完美,左右滑动的阻尼感和iOS原生体验有一点差距)。
为了这次验证项目的纯粹性,我最后没有依赖第三方Swipeable库,而是直接用Pressable加长按删除来实现删除操作。这样可以减少一个第三方依赖的兼容性风险,让核心链路更干净。
列表项的关键样式属性实测如下:
jsx复制const styles = StyleSheet.create({
card: {
backgroundColor: 'rgba(255, 255, 255, 0.92)',
borderRadius: 12,
padding: 16,
marginHorizontal: 16,
marginBottom: 12,
shadowColor: '#000',
shadowOpacity: 0.08,
shadowRadius: 8,
shadowOffset: { width: 0, height: 2 },
elevation: 3,
},
});
阴影效果在OpenHarmony上是支持的,但需要注意 shadowColor、shadowOpacity 这些iOS专属属性在部分鸿蒙设备上不一定完全生效,这时候 elevation(Android平台的阴影实现方式)往往能兜底。如果你想让卡片阴影在两个平台上都稳定显示,建议阴影三件套和 elevation 都写上,实测兼容性最好。
6. 核心机制:事件处理与自定义原生模块
6.1 rn调用电话功能:原生模块注册的完整流程
既然热搜词里有“rn调用电话功能”,这个在业务里也确实很常见,我就顺手在这个TodoList里加了一个“点击联系人条目拨打电话”的扩展功能。当然,这个功能本质上不是TodoList的必须项,但它很好地验证了另一个能力:RN for OpenHarmony能不能调用鸿蒙的原生API。
答案是能。RN的标准机制是“原生模块”——你在OpenHarmony侧用ArkTS写一个模块类,然后通过RN的TurboModule或者传统NativeModule机制暴露给JS层调用。RN for OpenHarmony对这套机制做了兼容,所以你可以把业务里需要用到系统能力的地方(比如打电话、发短信、读取联系人)封装成原生模块。
一个最简单的打电话模块实现思路如下。在OpenHarmony原生侧,你创建一个模块类,注册一个 callPhone(phoneNumber) 方法,内部通过 @ohos.telephony 的API发起呼叫。然后在JS侧,通过 NativeModules.CallModule.callPhone('10086') 来调用。
这里有几个注意事项:
- 打电话涉及系统权限,需要在OpenHarmony的module.json5里声明权限,比如
ohos.permission.PLACE_CALL。权限不配置的话,运行时会报权限不足的错误。 - 真机测试时,rk3568开发板如果插了SIM卡或者支持VoIP,打电话功能才能完整验证;如果是纯开发板环境,可以用
@ohos.telephony.radio的接口先做模拟验证。 - 原生模块的注册名称要保持一致——JS侧和原生侧的模块名必须完全匹配,否则调用的时候会报“NativeModule is null”的错误。
6.2 原生模块的桥接配置与调试技巧
如果你在项目中集成了自定义原生模块,调试时经常会遇到“模块找不到”或者“方法未定义”的问题。我的排查经验是:
第一,先确认JS侧的 NativeModules 和原生侧的模块注册名称完全一致。大小写都要对,这是最高频的错误。
第二,确认原生模块的导出方法名和JS侧调用名一致。RN的方法是自动映射的,方法名对不上就会报undefined。
第三,在原生侧加日志。DevEco Studio的Log窗口会输出OpenHarmony的HiLog日志,你可以在原生模块的方法入口处加一条日志,确认方法有没有被调用到。
第四,如果修改了原生代码,必须重新编译HAP包再安装,不能只刷新JS。因为原生模块的代码已经打包进HAP里,JS侧的热更新不会影响原生部分。
6.3 页面区域点击事件的完整示例
回到之前说的“点击页面其他区域执行某个函数”,我把最终的实现代码贴出来,大家可以直接抄:
jsx复制const handleOutsidePress = () => {
Keyboard.dismiss();
setSelectedId(null);
};
return (
<Pressable style={StyleSheet.absoluteFill} onPress={handleOutsidePress}>
<GradientBackground />
<SafeAreaView style={styles.container}>
<TextInput
ref={inputRef}
style={styles.input}
placeholder="请输入待办事项"
onFocus={() => setSelectedId(null)}
/>
<FlatList
data={todos}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}
extraData={selectedId}
/>
</SafeAreaView>
</Pressable>
);
这个结构最关键的地方在于:Pressable 是绝对定位覆盖全屏的,它是最底层的容器;GradientBackground 和 SafeAreaView 是正常的子视图,会接收触摸事件。点击子视图区域时,Pressable 的 onPress 不触发;点击空白区域时,由于没有子视图拦截,事件冒泡到 Pressable,onPress 正确触发。
这里有一个容易被忽略的点:Keyboard.dismiss() 在OpenHarmony上能不能用?实测是可以的,但要求当前窗口确实有键盘处于激活状态。如果键盘没弹出来,这个方法不会报错,但也不会产生任何效果。如果你在收起键盘之后还需要延迟执行某些UI操作(比如列表滚动到某一行),最好用 InteractionManager.runAfterInteractions() 来保证键盘动画结束之后再执行,避免动画冲突。
7. 数据持久化与扩展能力
7.1 本地存储方案:AsyncStorage现不现成
TodoList如果没有本地持久化,刷新一下就全没了,那体验太糟糕了。所以这个项目里我加了本地存储,用到的库是 @react-native-async-storage/async-storage。这个库在RN生态里的地位和 react-native-linear-gradient 一样,都属于“基本标配”。区别在于,AsyncStorage已经有OpenHarmony的适配版本了,所以可以正常使用。
用法和平常一模一样:
jsx复制import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = '@todo_list_data';
// 保存
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
// 读取
const raw = await AsyncStorage.getItem(STORAGE_KEY);
if (raw) {
setTodos(JSON.parse(raw));
}
我实测在OpenHarmony上,AsyncStorage的读写性能没有问题,数据会持久化到应用沙盒目录下。需要注意的是,AsyncStorage存储的是字符串,所以对象数据要先 JSON.stringify,读取的时候再 JSON.parse。如果你存的数据比较大(比如超过几百KB),建议考虑用SQLite或者文件存储方案,AsyncStorage在超大场景下会有性能瓶颈。
7.2 网络请求:axios还能不能用
移动应用基本都离不开网络请求。TodoList虽然不一定需要,但如果要扩展成云端同步,网络能力就是必须的。我顺手验证了一下 axios 在RN for OpenHarmony上的兼容性。
结论是:能用,但有个前提。axios本身是纯JS库,不涉及原生代码,所以它在OpenHarmony的JS运行时里可以直接跑。但它底层依赖的 XMLHttpRequest 或者 fetch,在OpenHarmony的JS引擎里必须有对应的实现。RN for OpenHarmony的运行时适配层对此做了兼容,所以在JS代码里直接写axios请求是没问题的。
不过,如果请求需要走HTTPS并且要验证证书,OpenHarmony的网络安全策略和Android/iOS不太一样,需要额外配置。
7.3 多页面跳转:React Navigation的兼容验证
TodoList虽然只有一个页面,但如果要做成完整的应用,多页面导航是刚需。我验证了React Navigation(具体是 @react-navigation/native 加 @react-navigation/native-stack)在OpenHarmony上的兼容性。实际测试下来,基本导航能力是OK的——可以正常push、pop页面,页面转场动画也有基本的淡入淡出效果。
但有两个问题需要注意。第一,如果用了 @react-navigation/bottom-tabs,底部的tab栏在OpenHarmony上的渲染效果和Android/iOS有一些差异,主要集中在图标对齐和文字的垂直居中上,需要额外调整样式。第二,如果用了React Navigation自带的header,定制header的样式属性(比如背景色、阴影)在OpenHarmony上不一定完全生效,有些需要你完全自定义header组件。
8. 常见问题与排查技巧实录
8.1 运行时报错速查表
在实际跑这个项目的过程中,我遇到了不少报错,这里整理成一张速查表,方便你对照排查。
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 应用启动直接闪退 | RN运行时版本不匹配 | 检查RN版本和OpenHarmony运行时版本是否对齐 |
| 页面白屏,Log无输出 | JS bundle加载失败 | 确认bundle路径配置正确,检查hap包内是否包含bundle |
| FlatList不更新 | 缺少extraData属性 | 在FlatList上添加extraData= |
| 渐变背景不显示 | 样式属性不支持 | 改用SVG或原生LinearGradient组件,不要直接写CSS渐变 |
| 原生模块调用报null | 模块名不一致 | 检查NativeModules注册名和JS侧调用名是否完全一致 |
| hdc连接不上设备 | hdcd服务未启动或USB调试未开 | 执行 hdc shell hilog 先确认设备通信是否正常 |
| 安装签名失败 | 签名文件过期或设备不确定 | 重新生成调试证书,确保UDID和设备匹配 |
8.2 开发板调试性能问题的排查方法
rk3568开发板属于中低端配置,跑RN应用的时候性能需要关注。如果你在rk3568上遇到明显的卡顿,我的排查思路是这样的。
先确认是不是JS层的性能瓶颈。可以用React DevTools的Performance面板分析JS逻辑,看看有没有大量的重复渲染或者状态更新。常见的问题是在FlatList的renderItem里创建了匿名函数,导致每次渲染都新建函数引用,引发不必要的子组件重渲染。解决办法是给列表项定义一个稳定的组件,并用memo包裹。
再确认是不是原生层的渲染瓶颈。在DevEco Studio的Profiler工具里查看UI渲染的帧耗时,如果单帧渲染耗时超过16ms,说明原生组件树太深或者样式计算太复杂。这种时候可以考虑简化列表项的结构,减少嵌套层级,避免复杂的阴影和模糊效果。
最后才是考虑设备本身的限制。rk3568跑GPU密集型任务确实吃力,如果渐变背景、阴影、复杂动画叠加在一起,卡顿在所难免。我的经验是:低端设备上尽量用静态渐变背景,不要叠加动态模糊,列表项保持简洁的视觉风格。
8.3 修改 const.product.name 之后带来的坑
这个坑是社区里不少人问过的。有些开发板出厂系统里的 const.product.name 不是标准的OpenHarmony设备名,导致DevEco Studio在签名的时候匹配不到设备。
我当时为了验证这个问题,手动修改了rk3568开发板的 const.product.name 参数,改完之后确实发现两个副作用。第一,之前安装过的应用全部失效,需要重新签名安装。第二,部分系统服务因为产品名不匹配无法正常启动,需要重启设备才能恢复。
所以我的忠告是:如果你不是特别清楚修改这个参数的后果,就不要动它。如果你必须修改,也要先备份原值,并且准备好重新刷机恢复的方案。
8.4 mongoose openharmony:听起来离谱但真有人搞
顺便提一下热搜词里的“mongoose openharmony”。Mongoose是Node.js生态里的MongoDB ODM库,理论上和OpenHarmony没有直接关系。但既然有人搜,我猜测可能是想在OpenHarmony设备上跑Node.js服务,然后用Mongoose操作MongoDB。这个场景确实存在,比如用开发板做IoT网关或边缘计算服务器。
在OpenHarmony上跑Node.js,目前比较可行的路径是使用Node.js对OHOS的移植版本,或者使用OpenHarmony的Linux内核模式直接运行标准Node.js。Mongoose本身是纯JS库,只要Node.js能用,Mongoose就能用。但需要注意OpenHarmony的轻量设备内存有限,跑Node.js服务会比较吃力,建议只在标准系统设备(rk3568及以上)上尝试。
9. 从 TodoList 到复杂应用的扩展思考
9.1 模块化与工程化拆分
TodoList只是一个起点。如果你真的要把RN for OpenHarmony用到生产环境,我建议提前做好工程化设计。
第一个是模块划分。不要把所有业务代码都堆在一个包里,应该按功能模块拆分。比如 todo 模块、user 模块、settings 模块,每个模块自带组件、状态、网络请求和路由配置。这样可以降低后期的维护成本,也方便做团队并行开发。
第二个是自动化构建。OpenHarmony应用打包成HAP之后,安装到真机的流程可以做成CI/CD流水线。至少要做到:代码push后自动触发编译、自动签名、自动部署到测试设备,并输出构建日志。我目前用hdc命令配合shell脚本实现了基础的自动化部署,再往上接Jenkins或者GitLab CI都不难。
第三个是调试链路。RN for OpenHarmony目前支持DevEco Studio的调试工具和React DevTools的远程调试,建议在开发环境里同时启用这两个工具,一个是看原生层日志,一个是看JS层状态,两端配合才能快速定位问题。
9.2 USB管理场景:usbmanager和libusb的启发
热搜词里有“openharmony usbmanager libusb的使用”,这看起来很技术,但和我做的TodoList有什么关系?其实关系在扩展能力上。OpenHarmony系统级的USB管理能力可以通过USBBus访问USB设备,libusb则是一个用户态的USB操作库。如果你在TodoList这种基础应用之外,需要跑智能硬件场景——比如在rk3588上控制一个USB摄像头、USB传感器,那你可以在OpenHarmony原生侧集成libusb,再通过RN的原生模块机制暴露给JS。
举个例子。你在原生侧写一个USBDeviceManager模块,调用libusb的接口打开设备、发送控制指令,然后通过RN的TurboModule导出 sendCommand(deviceId, command) 方法。JS侧的业务代码就可以通过RN语法直接控制硬件。这样你就能用RN写一个带硬件控制能力的应用界面,这想想还是很有价值的。
9.3 图标库和设计系统:lucide图标库的使用
关于热搜词里的“官方 lucide 图标库”,这个问题我也研究过。Lucide是一个开源的图标库,API友好的SVG图标集合。在RN for OpenHarmony里直接使用lucide是要费点功夫的,因为Lucide的标准版本面向Web,需要把SVG图标转换成RN支持的格式,或者配合react-native-svg渲染。
我的建议是:如果项目不大,直接用react-native-svg配合SvgXml组件加载lucide的SVG字符串,这样最灵活。如果项目大、图标多,建议把lucide的图标集生成React组件,放到一个独立的 Icon 组件库里统一管理。这里有个小技巧:lucide的图标SVG都是24x24的viewBox,加载之后可以用统一的stroke颜色和宽度来控制图标风格,保证整体视觉一致。
jsx复制import SvgXml from 'react-native-svg';
const CheckIcon = ({ color = '#000', size = 24 }) => (
<SvgXml width={size} height={size} xml={`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`} />
);
这样就能在TodoList里用上lucide的图标了,比如待办事项完成时显示一个对勾图标,未完成时显示一个圆圈图标。
10. 最后的实操建议
10.1 我的开发环境清单
给准备动手的朋友一份我当前的开发环境清单,照着这个组合踩坑的概率会小很多:
| 组件 | 推荐选项 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 20.04 / Windows 10+ | 两个平台我都试过,Ubuntu编译更快,但不强求 |
| IDE | DevEco Studio 4.0+ | 下载最新版,用内置SDK管理工具 |
| OpenHarmony SDK | API 10 / API 11 | 根据设备固件版本选择,别乱装 |
| RN版本 | 0.72 | 和RN for OpenHarmony仓库版本严格对应 |
| 开发板 | rk3568(入门)/ rk3588(性能充裕) | 预算够直接上rk3588,编译和运行都快不少 |
| 调试工具 | hdc + DevEco Profiler + React DevTools | 三件套缺一不可 |
10.2 几条掏心窝子的经验
做完整套验证,我最深的体会是:RN for OpenHarmony现在已经不是一个“能不能跑”的问题,而是“能跑多稳、能跑多远”的问题。基础组件、列表、样式、状态管理这些核心能力,都已经有可用的适配了,纯JS的第三方库也大部分能直接使用。但带原生代码的第三方库需要逐一验证,这是目前最大的成本。
第二个体会是:开发环境稳定压倒一切。我中间有一段时间因为DevEco Studio和SDK版本不匹配,折腾了两天才发现是版本问题。建议大家在项目一开始就固定好版本组合,并且用文档记录,团队所有成员统一环境。
第三个体会是关于设备的选择。rk3568开发板跑RN是能跑,但编译时间和运行性能都不太理想。如果你的预算允许,直接上rk3588,体验会好很多。另外,有条件的话准备两台设备,一台专门用来做自动化编译和部署的测试机,一台用来做手动交互验证,效率会高很多。
最后是心态层面的建议。如果你是从Android/iOS的RN开发转过来的,第一周你可能会觉得处处受限,很多熟悉的库用不了,很多原生的调试手段也变了。但我建议你多坚持一下,把核心链路跑通之后,你会发现RN的核心开发体验其实还在——写React组件、管状态、调接口,这些熟悉的节奏没有变。等你对OpenHarmony这套适配层的脾气摸熟了,开发效率和信心都会慢慢回来。
