react-native-elements 三方库鸿蒙版本适配与使用(MatePad Edge 双模式真机验证)
CPF-RN 社区地址:CPF-RN - 开源代码托管,代码协作 - AtomGit
上游三方库地址:https://github.com/react-native-elements/react-native-elements
npm 地址:https://www.npmjs.com/package/react-native-elements
适配后地址:rntpc_react-native-elements:基于 HarmonyOS NEXT 与 React Native 的 RNE 组件演示项目 - AtomGit
一、库概述
react-native-elements 是 React Native 生态里用得比较多的一个跨平台 UI 组件库,GitHub 上 Star 超过 2.5 万,上游官方版本支持 Android 和 iOS。这次适配的目标是让它能在 OpenHarmony / HarmonyOS NEXT 上跑起来,并且组件 API 保持不变,业务方迁移的时候不用改代码。
选这个库的原因也比较直接:它是纯 JavaScript/TypeScript 实现的,不包含原生模块,适配难度适中,适合作为 RNOH 适配的入门项目。整个库提供 30 多个开箱即用的组件,覆盖了移动端常见的 UI 场景:
按钮类:Button、ButtonGroup、Chip、FAB、SpeedDial,基于 TouchableOpacity 封装,支持主题定制和样式覆盖。
卡片与布局:Card、CardTitle、CardDivider、CardImage、Header、Divider、Tile、PricingCard,支持圆角、阴影、图片背景这些视觉效果。
表单与输入:Input、SearchBar、CheckBox、Switch、Slider,支持受控和非受控模式、验证状态、左侧图标。
列表与导航:ListItem 以及它的子组件(ListItemContent、ListItemTitle、ListItemSubtitle、ListItemChevron、ListItemCheckBox、ListItemInput、ListItemButtonGroup、ListItemAccordion、ListItemSwipeable),组合方式比较灵活。
反馈与弹窗:Badge、withBadge、Tooltip、Overlay、Dialog、BottomSheet、AirbnbRating。
图标支持:Icon 组件封装了 react-native-vector-icons,支持 Material、Ionicons、FontAwesome 等多种图标字体。

适配的时候需要把上游仓库 clone 到国内 AtomGit,操作效率更高,也方便后续提交 PR。

二、适配环境与真机设备
这次适配用的版本组合如下:
|
项 |
版本 |
|
React Native |
0.82.1 |
|
RNOH(@react-native-oh/react-native-harmony) |
0.82.30 |
|
React |
19.1.1 |
|
DevEco Studio |
6.0.0.858+ |
|
HarmonyOS SDK |
6.1.1(24),runtimeOS: HarmonyOS |
|
Node.js |
20+(Metro 打包建议用 Node 24) |
|
上游库版本 |
react-native-elements 3.4.3 |


全部适配和验证工作都是在华为 MatePad Edge 二合一真机上完成的,没有用模拟器。这个设备支持平板模式和电脑模式(PC 模式)两种使用形态,两种模式下都跑了安装、运行和组件验证。


设备的鸿蒙系统版本:

三、适配过程
3.1 环境搭建
RNOH 开发环境搭建直接参考 CPF-RN 社区组织下的环境搭建文档就行,这里不重复展开:
CPF-RN 社区地址:CPF-RN - 开源代码托管,代码协作 - AtomGit
环境搭好之后,确认上面列的版本都对上了,就可以开始拉代码适配了。
3.2 适配步骤
react-native-elements 是纯 JS 层的库,不涉及原生模块(C++/ArkTS),所以所有改动都集中在 JS 源码和 Demo 工程的配置里。先拉代码、建分支:
git clone https://github.com/react-native-elements/react-native-elements.git
cd react-native-elements
git checkout v3.4.3
git checkout -b feat/ohos_react-native-elements_3.4.3

分支命名按社区规范来:feat/ohos_库名称_版本号。
整个适配过程改动的文件汇总在下面这张表里,后面再挑几个关键的点详细说:
|
改动文件 |
改动原因 |
改动内容 |
|
src/helpers/index.tsx |
Platform.OS 在鸿蒙端是 harmony/ohos,原有的 ios/android 判断走 else 分支,行为不可控 |
新增 isHarmony、isAndroidLike 两个常量,组件中统一使用,鸿蒙走 Android 样式路径 |
|
src/config/withTheme.tsx |
上游用 ThemeConsumer render-props 模式,React 19 下 Context Consumer 路径异常,导致组件不显示或主题不生效 |
完全重写为 useContext(ThemeContext) + forwardRef,一次解决全部 30+ 组件的主题获取 |
|
src/switch/Switch.tsx |
上游通过 isIOS 区分 iOS 和 Android 的轨道/滑块颜色,鸿蒙需要走 Android 样式 |
将 Platform.OS 判断改为 isIOS 常量,鸿蒙自动走 Android 分支 |
|
src/list/ListItemChevron.tsx |
列表项右箭头,iOS 用 Ionicons,Android/鸿蒙用 Material 图标 |
用 isIOS 判断图标 type 和 name,鸿蒙用 Material 的 keyboard-arrow-right |
|
src/dialog/DialogTitle.tsx |
对话框标题字重,iOS 为 500,Android/鸿蒙为 700 |
用 isIOS 判断 fontWeight,鸿蒙自动走 700 |
|
src/header/Header.tsx |
上游用 react-native-safe-area-context 的 SafeAreaView,依赖原生视图 RNCSafeAreaView,RNOH 0.82 下不存在,导致崩溃 |
将 SafeAreaView 的导入改为从 react-native 核心导入 |
|
src/bottomSheet/BottomSheet.tsx |
同上,BottomSheet 也用了 safe-area-context 的 SafeAreaView |
同样改用 RN 核心 SafeAreaView |
|
src/list/ListItem.tsx |
React.Children.map 渲染子元素时未指定 key,React 19 下产生 key 警告 |
映射后的子元素包裹在带 key 的 React.Fragment 中 |
|
src/tooltip/Tooltip.tsx |
计算弹出位置时只处理了 iOS 和 Android 的状态栏偏移 key,鸿蒙端弹出位置不对 |
新增 harmony 和 ohos 的状态栏偏移 key |
|
harmony/entry/src/main/ets/pages/Index.ets |
vector-icons 的 TTF 字体在鸿蒙端不会自动加载,Icon/CheckBox/Chevron 不显示;jsBundleProvider 优先 Metro 会导致首启白屏 |
fontResourceByFontFamily 注册实际使用的 TTF 字体;jsBundleProvider 优先 ResourceJSBundleProvider,Metro 仅作 debug 备选 |
下面挑几个关键的改动点展开说一下。
(1)平台判断 Helper
这是适配的第一步。上游代码里到处都是 Platform.OS === 'ios' 这样的判断,在 RNOH 环境下 Platform.OS 的值是 'harmony' 或 'ohos',既不是 ios 也不是 android,所有 else 分支的行为都不可控。所以在 helpers/index.tsx 里加了两个统一的常量,后面所有组件都用这两个常量来判断:
import { Platform } from 'react-native';
const isIOS = Platform.OS === 'ios';
const isHarmony =
(Platform.OS as string) === 'harmony' || (Platform.OS as string) === 'ohos';
const isAndroidLike = Platform.OS === 'android' || isHarmony;
export { isIOS, isHarmony, isAndroidLike };

这里有个细节要注意:isAndroidLike 只用于样式路径(鸿蒙复用 Android 的视觉风格),不能用于 TouchableNativeFeedback / Ripple——水波纹是 Android 独有的原生触摸反馈,鸿蒙端应该回退到 TouchableOpacity。
(2)withTheme React 19 重写
这是整个适配里最核心的一个改动,也是最隐蔽的坑。上游的 withTheme 用的是 ThemeConsumer 的 render-props 模式(children-as-function),这种写法在 React 16 时代没问题,但 RNOH 0.82 配套的是 React 19.1.1,新的 Context Consumer 路径下 render-props 模式会出现渲染异常,表现就是所有组件都不显示或者主题不生效。
排查这个问题花了不少时间,一开始以为是组件本身的问题,后来才定位到是 withTheme 这一层。解决办法是直接重写为 useContext Hook + forwardRef:
import React, { useContext } from 'react';
import deepmerge from 'deepmerge';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { ThemeContext, ThemeProps } from './ThemeProvider';
import DefaultTheme, { FullTheme } from './theme';
const isClassComponent = (Component: any) =>
Boolean(Component.prototype && Component.prototype.isReactComponent);
const noop = () => {};
function withTheme<P = {}, T = {}>(
WrappedComponent: React.ComponentType<P & Partial<ThemeProps<T>>>,
themeKey: string
) {
const name = themeKey
? `Themed.${themeKey}`
: `Themed.${WrappedComponent.displayName || WrappedComponent.name || 'Component'}`;
const Component = WrappedComponent as React.ComponentType<any>;
const Themed = React.forwardRef<any, any>((props, forwardedRef) => {
const { children, ...rest } = props;
const context = useContext(ThemeContext);
const theme = context?.theme ?? DefaultTheme;
const updateTheme = context?.updateTheme ?? noop;
const replaceTheme = context?.replaceTheme ?? noop;
const newProps = {
theme,
updateTheme,
replaceTheme,
...deepmerge<FullTheme>(
(themeKey && (theme[themeKey as keyof Partial<FullTheme>] as Partial<FullTheme>)) || {},
rest,
{ clone: false }
),
children,
};
if (isClassComponent(WrappedComponent)) {
return <Component ref={forwardedRef} {...newProps} />;
}
return <Component {...newProps} />;
});
Themed.displayName = name;
if (isClassComponent(WrappedComponent)) {
return hoistNonReactStatics(Themed, WrappedComponent);
}
return Themed as any;
}
export default withTheme;

重写之后,所有通过 withTheme 包裹的组件(Button、Card、Input 等 30 多个)都能在 React 19 下正确拿到主题,同时保持了对类组件和函数组件的兼容,forwardRef 也确保了 ref 能正确传递到被包裹的组件。这一个改动解决了全部组件的主题获取问题,比逐个组件打补丁要干净得多。
(3)SafeAreaView 替换
上游的 Header 和 BottomSheet 组件用了 react-native-safe-area-context 提供的 SafeAreaView,这个库依赖原生视图 RNCSafeAreaView。在 RNOH 0.82 环境下,这个原生视图不存在,页面直接崩溃或者布局异常。
解决办法很直接:把 SafeAreaView 的导入从 react-native-safe-area-context 改成从 react-native 核心导入。RN 核心自带的 SafeAreaView 不依赖额外原生模块,在 RNOH 下可以直接用,功能上比 safe-area-context 版本少一些(不支持 edges 自定义),但满足 Header 和 BottomSheet 的基本安全区需求没问题。
// 修改前
import { SafeAreaView } from 'react-native-safe-area-context';
// 修改后
import { SafeAreaView } from 'react-native';
(4)组件样式适配
这部分改动比较琐碎,但逻辑都差不多:鸿蒙和 Android 同为移动端,视觉风格接近,大部分组件的样式直接复用 Android 分支就行。
比如 Switch 组件,上游通过 isIOS 区分 iOS 和 Android 的轨道/滑块颜色,把 Platform.OS 判断改成用 isIOS 常量,鸿蒙就自动走 Android 分支了。ListItemChevron 的右箭头也是同理,iOS 用 Ionicons,Android/鸿蒙用 Material 图标。DialogTitle 的字重 iOS 是 500,Android/鸿蒙是 700,同样用 isIOS 判断就行。

(5)字体注册配置
react-native-elements 的 Icon、CheckBox、ListItemChevron 这些组件都依赖 react-native-vector-icons 渲染图标。在鸿蒙端,TTF 字体文件不会自动加载,必须在 Demo 工程的 Index.ets 里通过 fontResourceByFontFamily 手动注册。
注意只注册实际用到的字体集,避免 HAP 包体积过大。这个 Demo 用了 MaterialIcons 和 MaterialCommunityIcons 两套字体。同时 jsBundleProvider 的顺序也要注意,优先用 ResourceJSBundleProvider 读本地离线 bundle,Metro 只作为 debug 模式下的备选,不然首启会白屏很久(这个问题在第四章详细说)。

(6)其他细节
还有几个小改动:ListItem 里 React.Children.map 渲染子元素时没加 key,React 19 下会报警告,给映射后的子元素包一层带 key 的 Fragment 就行。Tooltip 计算弹出位置时只处理了 iOS 和 Android 的状态栏偏移 key,加上 harmony 和 ohos 的 key。Button 在 Android 上用 TouchableNativeFeedback 实现水波纹,鸿蒙没有这个原生组件,通过 Platform.select 的 default 分支自动回退到 TouchableOpacity,不用额外改代码,文档里说明一下行为差异就行。
3.3 适配效果
适配完成后,建了一个独立的 RNOH Demo 工程来验证全部核心组件。Demo 工程分三个展示区域:
静态展示区(StaticShowcase):Button(主按钮/描边按钮)、Card(卡片 + Avatar + Badge 组合)、ListItem(带 Chevron 箭头)、Icon(带图标的按钮),用 React.memo 包裹,避免交互区状态变化时静态区跟着重渲染。
交互区(InteractivePanel):Input(带左侧图标)、SearchBar、CheckBox、Switch、Slider(拖动实时显示数值),所有交互状态保持在组件内部。
动画遮罩(FadeOverlay):用 React Native 核心 Modal + Animated 实现淡入缩放效果,useNativeDriver: true 开原生线程驱动,不卡 JS 线程。
Demo 工程的入口 App.tsx 用 ThemeProvider 包裹整个应用,把三个区域组合起来:

静态展示区的代码,展示了 Button、Card、Avatar、Badge、ListItem 这些组件的基本用法:

交互区的代码,Input、SearchBar、CheckBox、Switch、Slider 都在这里:

动画遮罩的代码,Modal + Animated 实现淡入缩放,点击遮罩或按钮关闭:

用 DevEco Studio 打开 Demo 工程的 harmony 目录,USB 连 MatePad Edge 真机,点 Run 编译安装。首次编译大概 5 到 8 分钟(包含原生 so 库编译),后面增量编译 30 秒左右。


全部组件在 MatePad Edge 的平板模式(触摸全屏)和 PC 模式(鼠标键盘窗口化)下都跑了验证。
平板模式验证结果:
|
用例 |
操作 |
预期 |
结果 |
|
Button 主按钮 |
触摸点击 |
透明度反馈,onPress 触发 |
通过 |
|
Button 描边按钮 |
触摸点击 |
边框样式正常,点击触发 Overlay |
通过 |
|
Card 卡片 |
视觉检查 |
圆角、elevation 阴影、标题分割线正常 |
通过 |
|
Avatar 头像 |
视觉检查 |
圆形头像,文字"鸿"居中 |
通过 |
|
Badge 徽标 |
视觉检查 |
"OHOS"绿色徽标正常显示 |
通过 |
|
Input 输入框 |
软键盘输入文字 |
输入正常,左侧 person 图标显示 |
通过 |
|
SearchBar 搜索框 |
软键盘输入 |
搜索框样式正常,可输入文字 |
通过 |
|
CheckBox 复选框 |
触摸切换 |
勾选状态切换,Material 勾选图标显示 |
通过 |
|
Switch 开关 |
触摸切换 |
开关切换,Android 风格轨道/滑块颜色正确 |
通过 |
|
Slider 滑块 |
触摸拖动 |
滑块可拖动,数值实时更新,allowTouchTrack 点击轨道跳转 |
通过 |
|
ListItem 列表项 |
视觉检查 + 触摸 |
布局正常,右侧 Material Chevron 箭头显示 |
通过 |
|
Overlay 遮罩 |
点击"打开 Overlay"按钮 |
Modal 弹出,淡入缩放动画流畅,点击遮罩/按钮关闭 |
通过 |
|
页面滚动 |
上下滑动 |
ScrollView 滚动流畅,无卡顿 |
通过 |
MatePad Edge react-native平板展示
PC 模式验证结果:
|
用例 |
操作 |
预期 |
结果 |
|
窗口化布局 |
调整窗口大小 |
内容 maxWidth 限制生效,不过度拉伸,布局自适应 |
通过 |
|
鼠标点击 Button |
鼠标左键点击 |
点击反馈正常,onPress 触发 |
通过 |
|
物理键盘输入 |
Input/SearchBar 中用物理键盘打字 |
输入正常,光标跟随 |
通过 |
|
鼠标拖动 Slider |
鼠标按住滑块拖动 |
拖动流畅,数值实时更新 |
通过 |
|
滚轮滚动页面 |
鼠标滚轮上下滚动 |
页面滚动正常 |
通过 |
|
Modal 居中显示 |
打开 Overlay |
Modal 在窗口内居中显示,遮罩覆盖整个窗口 |
通过 |
|
CheckBox/Switch 鼠标切换 |
鼠标点击切换 |
状态切换正常 |
通过 |
MatePad Edge react-native电脑展示
组件支持状态汇总:
|
组件 |
状态 |
备注 |
|
Button / ButtonGroup / Chip / FAB / SpeedDial |
支持 |
鸿蒙使用 TouchableOpacity,无 Ripple 水波纹 |
|
Card / CardTitle / CardDivider / CardImage |
支持 |
圆角和 elevation 阴影正常 |
|
Input |
支持 |
左侧图标需配置字体 |
|
Avatar / Accessory |
支持 |
|
|
Badge / withBadge |
支持 |
|
|
SearchBar |
支持 |
鸿蒙端使用 platform="default" 或 "android" |
|
ListItem 及全部子组件 |
支持 |
Chevron 使用 Material 图标 |
|
CheckBox / CheckBoxIcon |
支持 |
需配置 vector-icons 字体 |
|
Switch |
支持 |
Android 风格样式 |
|
Slider |
支持 |
allowTouchTrack 正常 |
|
Divider |
支持 |
|
|
Header |
支持 |
改用 RN 核心 SafeAreaView |
|
LinearProgress |
支持 |
useNativeDriver 动画正常 |
|
Tab / TabView |
支持 |
建议视觉验证 |
|
SocialIcon / PricingCard / Tile / Rating |
支持 |
图标需配置字体 |
|
Icon |
有限支持 |
依赖 react-native-vector-icons + 字体注册,未注册的字体集不显示 |
|
Overlay / Dialog / BottomSheet / Tooltip |
待充分验证 |
依赖 RN Modal,基本功能可用,复杂动画和边缘情况需进一步验证 |
|
ListItemSwipeable |
待验证 |
依赖 react-native-gesture-handler,需确认该库的鸿蒙适配状态 |
对业务方来说,迁移成本基本为零:组件 API 和上游完全一致,只需要把依赖来源从 npm 换成 AtomGit 上的适配版本,在 Index.ets 里注册一下用到的图标字体,其他代码不用动。
四、常见问题与解决方案
问题一:真机安装后闪退,报 libRNOHApp is undefined
现象
打包安装到 MatePad Edge 真机后,应用启动瞬间闪退。通过 hdc hilog 看日志,核心报错是:
Couldn't create bindings between ETS and CPP. libRNOHApp is undefined.
load librnoh_app.so failed ... No such file or directory
原因
这个报错和业务代码无关,是 HAP 包里的 native 库 ABI 和真机 CPU 架构不匹配。
RNOH 启动时,ArkTS 侧需要通过 NAPI 加载 librnoh_app.so 来建立 ETS 和 C++ 的绑定。如果 HAP 包里没有当前设备架构对应的 so 文件,加载就会失败,libRNOHApp 变成 undefined,初始化直接 Fatal,进程退出。
具体到这个项目,一开始为了加快编译安装速度,把 harmony/entry/build-profile.json5 里的 abiFilters 只保留了 x86_64。但 MatePad Edge 真机是 arm64-v8a 架构,打出来的 HAP 里只有 libs/x86_64/librnoh_app.so,没有 libs/arm64-v8a/librnoh_app.so,真机启动时找不到对应 so,立刻闪退。
解决方法
1. 修改 harmony/entry/build-profile.json5,把 abiFilters 改成同时包含 arm64-v8a 和 x86_64:
"abiFilters": ["arm64-v8a", "x86_64"]

真机(手机/平板)需要 arm64-v8a,PC 模拟器需要 x86_64,双 ABI 一包两用。
2. 改完 abiFilters 后一定要 Clean 再全量编译。只改配置不清理的话,cmake 可能继续用之前的缓存,打出来的还是旧 ABI 的包。在 DevEco Studio 里用 Build → Clean Project,然后再 Rebuild。
3. 打包完成后,可以解压 HAP 文件(本质是 zip)确认里面是否包含两个架构的 so:
libs/arm64-v8a/librnoh_app.so
libs/x86_64/librnoh_app.so

4. 确认无误后再安装到真机。
小结:遇到 libRNOHApp is undefined,先查 ABI 配置和 HAP 里的 so 文件,不要先去翻业务组件代码。为了提速只打单 ABI 是真机闪退的常见原因。
问题二:应用启动后长时间白屏,很久才出内容
现象
应用能正常安装和启动,但首屏长时间白屏(大概 30 秒到 1 分钟),然后才突然显示出页面内容。期间没有报错,进程也没有退出,看起来像是卡住了。
原因
这个问题出在 JS Bundle 的加载策略上。RNOH 页面初始化时通过 JSBundleProvider 来拉取 JS 包。如果配置里优先走 MetroJSBundleProvider(调试用的 Metro 服务),设备会尝试连接开发机的 Metro 服务(默认 8081 端口)。
当 Metro 服务没开、或者设备和开发机之间网络不通、或者端口没做反向代理时,连接会一直超时重试。等超时结束后,才会 fallback 到本地资源包。这就导致了"先进去白屏很久,然后才有内容"的现象。
具体到这个项目,一开始 harmony/entry/src/main/ets/pages/Index.ets 里 jsBundleProvider 的顺序是 Metro 优先,真机演示时 Metro 没开,就出现了长时间白屏。
解决方法
1. 调整 Index.ets 里 jsBundleProvider 的顺序,优先用 ResourceJSBundleProvider(读本地 rawfile 里的 bundle.harmony.js),MetroJSBundleProvider 只作为 debug 模式下的备选:
jsBundleProvider: this.rnohCoreContext?.isDebugModeEnabled
? new AnyJSBundleProvider([
new ResourceJSBundleProvider(
this.rnohCoreContext.uiAbilityContext.resourceManager,
'bundle.harmony.js'
),
new MetroJSBundleProvider(),
])
: new ResourceJSBundleProvider(
this.rnohCoreContext.uiAbilityContext.resourceManager,
'bundle.harmony.js'
),

这样一进 App 就直接用打进 HAP 的离线 bundle 秒开,Metro 只在需要热更新时才连接,不再卡首启。
2. 确保本地 bundle 已经打包进工程。发布或真机演示前,先执行 Metro 打包命令,把生成的 bundle.harmony.js 放到 harmony/entry/src/main/resources/rawfile/ 目录下,再编 HAP。
3. 如果需要热更新调试,再手动开 Metro:
npm start
然后用 hdc 做端口反向代理:
hdc rport tcp:8081 tcp:8081
注意 Metro 对 Node 版本有要求,这个项目里用 Node 24 可以正常跑,DevEco 自带的 Node 18 可能会有兼容性问题。
小结:启动白屏优先查 Bundle Provider 的顺序。真机演示应该默认优先本地 Resource Bundle,Metro 只能当开发热更新通道,不能作为首启的硬依赖,否则弱网或没开 Metro 时就会出现长时间白屏。
五、总结
这次 react-native-elements 的鸿蒙适配走下来,最大的感受是:纯 JS UI 库的适配门槛不高,但真机调试的坑比想象中多。
代码层面主要是加平台判断、处理 React 19 兼容性、替换 SafeAreaView、注册图标字体,没有涉及原生模块,适合作为 RNOH 适配的入门练手项目。其中 withTheme 的 React 19 重写是最核心也最隐蔽的一个改动,排查花了不少时间。
真正花时间的是真机调试——ABI 配置不对导致闪退、Bundle 加载顺序不对导致白屏,这两个问题在 PC 模拟器上都复现不出来。所以适配完一定要上真机跑一遍,不能只在模拟器上验证。
整体来看,react-native-elements 是一个性价比很高的适配项目,难度适中,覆盖面广,做完对 RNOH 的整个开发流程会有比较完整的理解。
更多推荐


所有评论(0)