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
适配后地址:https://gitcode.com/aasd23/rntpc_react-native-elements
react-native-elements 库概述
react-native-elements 是 React Native 生态中使用广泛的跨平台 UI 组件库,GitHub Star 超过 2.5 万,官方版本支持 Android 和 iOS。通过本次适配,该库已可在 OpenHarmony / HarmonyOS NEXT 平台上运行。
主要功能:提供 30 余个开箱即用的 UI 组件,覆盖按钮、卡片、输入框、头像、徽标、搜索栏、列表、复选框、开关、滑块、工具提示、底部弹窗等常见移动端 UI 场景。
跨平台 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,操作效率更高。

适配基础环境
React Native 版本:0.82.1
RNOH(React Native for OpenHarmony)版本:0.82.30
React 版本:19.1.1
HarmonyOS SDK:6.1.1(24),runtimeOS: HarmonyOS
DevEco Studio:6.0.0.858+
Node.js:20+(Metro 打包建议使用 Node 24)
上游库版本:react-native-elements 3.4.3


演示真机与系统版本
本次适配全部基于华为 MatePad Edge 二合一真机验证,未使用模拟器。该设备支持平板模式和电脑模式(PC 模式)两种使用形态,本次在两种模式下均完成了安装、运行和组件验证。


演示的鸿蒙系统版本:

适配过程
一、前言
react-native-elements 是纯 JavaScript/TypeScript 实现的 UI 组件库,上游版本(3.4.3)仅支持 Android 和 iOS。鸿蒙化目标:
- 组件 API 完全不变——业务方迁移时零改动,仅更换依赖来源,所有组件的 props、事件、用法与上游保持一致;
- 纯 JS 层适配——不新增原生模块(C++/ArkTS),所有平台差异收敛在 JS 层的平台判断、样式兼容和依赖处理中;
- React 19 兼容——RNOH 0.82 配套 React 19.1.1,上游库开发时基于 React 16,需修复 Context Consumer、key 警告等兼容性问题;
- 双模式真机验证——在 MatePad Edge 的平板模式(触摸全屏)和电脑模式(鼠标键盘窗口化)下均完成全部核心组件的渲染和交互验证;
- 平台语义差异显式处理——触摸反馈(Ripple)、图标字体、安全区、状态栏偏移等平台差异做明确的映射和文档说明。
二、基础环境
|
项 |
版本 |
|
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+ |
|
OpenHarmony SDK |
6.1.1(24),runtimeOS: HarmonyOS |
|
原生编译器 |
BiSheng |
|
Node.js |
20+(Metro 建议 Node 24) |
|
真机 |
华为 MatePad Edge 二合一(平板模式 + PC 模式) |
|
上游库版本 |
react-native-elements 3.4.3 |
|
图标依赖 |
react-native-vector-icons 10.2.0 |
|
安全区依赖 |
react-native-safe-area-context 5.5.2 |
三、库分析
3.1 库结构
react-native-elements v3.4.3 的源码全部位于 src/,是纯 TypeScript 实现,跨平台通用,适配过程中不修改组件的对外 API,仅调整平台判断和兼容性问题:
src/
├── index.ts # 入口,导出全部组件
├── avatar/ # Avatar、Accessory
├── badge/ # Badge、withBadge
├── bottomSheet/ # BottomSheet
├── buttons/ # Button、ButtonGroup、Chip、FAB、SpeedDial
├── card/ # Card、CardTitle、CardDivider、CardImage 等
├── checkbox/ # CheckBox、CheckBoxIcon
├── config/ # ThemeProvider、withTheme、theme、colors、makeStyles
├── dialog/ # Dialog、DialogTitle、DialogActions、DialogButton、DialogLoading
├── divider/ # Divider
├── header/ # Header
├── helpers/ # renderNode、getIconType、normalizeText、平台判断
├── icons/ # Icon(封装 react-native-vector-icons)
├── image/ # Image
├── input/ # Input
├── linearProgress/ # LinearProgress
├── list/ # ListItem 及全部子组件
├── overlay/ # Overlay
├── pricing/ # PricingCard
├── searchbar/ # SearchBar
├── slider/ # Slider
├── social/ # SocialIcon、SocialIconButton
├── switch/ # Switch
├── tab/ # Tab、TabView
├── text/ # Text
├── tile/ # Tile
└── tooltip/ # Tooltip

全部组件通过 withTheme 高阶组件包裹,从 ThemeContext 获取主题配置。组件本身基于 React Native 核心组件(View、Text、TextInput、TouchableOpacity、Switch、Slider、Modal、Animated 等)封装,不包含任何 iOS 或 Android 原生模块代码。
3.2 核心依赖分析
react-native-elements 有两个 peerDependencies,直接影响鸿蒙适配:
|
依赖 |
版本要求 |
用途 |
鸿蒙适配状态 |
|
react-native-vector-icons |
> 7.0.0 |
Icon 组件、CheckBox 勾选图标、ListItemChevron 箭头、Button/SearchBar 左侧图标等 |
已有鸿蒙适配版,需在 Index.ets 中通过 fontResourceByFontFamily 注册 TTF 字体 |
|
react-native-safe-area-context |
>= 3.0.0 |
Header、BottomSheet 组件的安全区处理 |
RNOH 0.82 下原生视图 RNCSafeAreaView 缺失,需改用 RN 核心 SafeAreaView |
运行时依赖(color、deepmerge、hoist-non-react-statics、lodash.isequal、react-native-ratings、react-native-size-matters)均为纯 JS 库,在 RNOH 环境下可直接使用,无需额外适配。
3.3 平台判断
上游代码中通过 Platform.OS === 'ios' 区分 iOS 和 Android 的样式与行为。在 RNOH 环境下,Platform.OS 的值为 'harmony' 或 'ohos',既不是 'ios' 也不是 'android',所有 else 分支的行为不可控。


全局排查后,需要修改的平台判断点集中在以下组件:
|
组件 |
平台判断位置 |
iOS 行为 |
Android/鸿蒙行为 |
|
ListItemChevron |
图标 type 和 name |
Ionicons chevron-forward-outline |
Material keyboard-arrow-right |
|
DialogTitle |
fontWeight |
500 |
700 |
|
Switch |
trackColor、thumbColor |
iOS 风格开关 |
Android 风格开关 |
|
Tooltip |
状态栏偏移 key |
ios 偏移量 |
android/鸿蒙偏移量 |
|
Button |
Touchable 组件选择 |
TouchableOpacity |
Android 用 TouchableNativeFeedback(Ripple),鸿蒙回退 TouchableOpacity |
3.4 React 19 兼容性
RNOH 0.82 配套 React 19.1.1,而 react-native-elements 3.4.3 上游开发时使用 React 16。其中 withTheme 高阶组件使用了 ThemeConsumer 的 render-props 模式(children-as-function),在 React 19 的新 Context Consumer 路径下会出现渲染异常。这是适配过程中最隐蔽的一个兼容性问题,表现为所有组件不显示或主题不生效。
此外,ListItem 和 PadView 中通过 React.Children.map 渲染子元素时未显式指定 key,React 19 下会产生 key 警告并可能影响性能。
四、适配方案设计
4.1 总体思路
┌─────────────────────────────────────────────────────────────┐
│ 业务层(不变) │
│ import { Button, Card, Input } from 'react-native-elements' │
├─────────────────────────────────────────────────────────────┤
│ react-native-elements 适配层(JS 层修改) │
│ ├── helpers/index.tsx → 新增 isHarmony / isAndroidLike │
│ ├── config/withTheme.tsx → React 19 兼容(useContext 重写)│
│ ├── switch/Switch.tsx → 鸿蒙走 Android 样式路径 │
│ ├── list/ListItemChevron → 鸿蒙用 Material 图标 │
│ ├── dialog/DialogTitle → 鸿蒙 fontWeight 700 │
│ ├── header/Header.tsx → 改用 RN 核心 SafeAreaView │
│ ├── bottomSheet/ → 改用 RN 核心 SafeAreaView │
│ ├── tooltip/Tooltip.tsx → 新增 harmony/ohos 状态栏偏移 │
│ └── list/ListItem.tsx → 子元素加 key(React 19 警告) │
├─────────────────────────────────────────────────────────────┤
│ RNOH 运行时(0.82.30) │
│ React Native 核心组件 + ArkTS 桥接 + HarmonyOS NEXT │
└─────────────────────────────────────────────────────────────┘

- 纯 JS 层适配,不新增原生模块——react-native-elements 本身不包含原生代码,所有平台差异均可在 JS 层通过 Platform 判断和样式覆盖解决,不需要编写 C++ 或 ArkTS 原生模块;
- 鸿蒙复用 Android 样式路径——鸿蒙和 Android 同为移动端,视觉风格接近,大部分组件的样式(阴影、字重、开关样式、图标类型)直接复用 Android 分支,通过 isAndroidLike 常量统一判断;
- TouchableNativeFeedback 保持 Android 独占——水波纹效果是 Android 独有,鸿蒙端通过 Platform.select 的 default 分支自动回退到 TouchableOpacity,不强行适配 Ripple;
- 图标字体在鸿蒙工程侧注册——vector-icons 的 TTF 字体不在 JS 库内处理,而是在 Demo 工程的 Index.ets 中通过 fontResourceByFontFamily 注册,与 RNOH 的字体加载机制对齐;
- withTheme 完全重写而非补丁——React 19 下 ThemeConsumer render-props 模式不兼容,直接重写为 useContext + forwardRef,一次解决所有组件的主题获取问题,而不是逐个组件打补丁。
4.2 核心适配设计
|
适配点 |
问题 |
方案 |
影响范围 |
|
平台判断 Helper |
Platform.OS 为 harmony/ohos,所有 ios/android 判断的 else 分支不可控 |
新增 isHarmony、isAndroidLike 常量,组件中统一使用,鸿蒙走 Android 样式路径 |
Switch、ListItemChevron、DialogTitle、Tooltip 等 |
|
withTheme React 19 兼容 |
ThemeConsumer render-props 在 React 19 下渲染异常,所有组件不显示 |
重写为 useContext(ThemeContext) + forwardRef,保持类组件和函数组件兼容 |
全部 30+ 组件(都通过 withTheme 包裹) |
|
SafeAreaView 原生视图缺失 |
react-native-safe-area-context 的 RNCSafeAreaView 在 RNOH 0.82 下不存在,Header/BottomSheet 崩溃 |
改用 React Native 核心自带的 SafeAreaView,不依赖额外原生模块 |
Header、BottomSheet |
|
图标字体加载 |
vector-icons 的 TTF 字体在鸿蒙端不会自动链接,Icon/CheckBox/Chevron 不显示 |
在 Index.ets 的 fontResourceByFontFamily 中注册所需 TTF,只注册实际使用的字体集 |
Icon、CheckBox、ListItemChevron、Button/SearchBar 左侧图标 |
|
React 19 key 警告 |
ListItem/PadView 的 Children.map 未指定 key,React 19 警告并可能影响性能 |
映射后的子元素包裹在带 key 的 Fragment 中 |
ListItem、PadView |
|
Tooltip 状态栏偏移 |
Tooltip 计算弹出位置时只处理了 ios/android 的状态栏偏移 key |
新增 harmony/ohos 的状态栏偏移 key |
Tooltip |
|
Button 触摸反馈 |
Android 用 TouchableNativeFeedback(Ripple),鸿蒙无此原生组件 |
通过 Platform.select default 分支自动回退 TouchableOpacity,文档中明确行为差异 |
Button、ButtonGroup、Chip、FAB、SpeedDial |
五、适配流程
5.1 代码拉取与创建分支
从上游 GitHub 克隆代码,切换到稳定版标签,创建鸿蒙适配分支:
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_库名称_版本号。
适配完成后,在仓库根目录补充以下文件:
README.OpenSource.md——开源声明,包含上游仓库信息、适配版本、适配摘要、组件支持状态表、依赖说明;README_zh.md——中文使用说明,涵盖鸿蒙端安装、字体配置、组件使用方法;CHANGELOG_ohos.md——鸿蒙适配变更日志,记录所有新增、修改和修复项。
5.2 Demo 工程创建
创建独立的 RNOH Demo 工程用于验证适配后的组件库。使用 React Native CLI 初始化工程,再按照 RNOH 接入文档添加 harmony 目录:
npx react-native@0.82 init RNElementsDemo
cd RNElementsDemo

工程结构:
RNElementsDemo/
├── App.tsx # 入口,ThemeProvider + 页面布局
├── src/components/
│ ├── StaticShowcase.tsx # 静态组件展示(Button/Card/Avatar/ListItem)
│ ├── InteractivePanel.tsx # 交互组件(Input/SearchBar/CheckBox/Switch/Slider)
│ └── FadeOverlay.tsx # Modal + 动画遮罩演示
├── harmony/ # 鸿蒙工程(DevEco Studio 打开此目录)
│ ├── entry/src/main/ets/pages/Index.ets # 入口,注册字体 + RNApp
│ ├── entry/build-profile.json5 # ABI 配置(arm64-v8a + x86_64)
│ └── build-profile.json5 # 构建配置
└── package.json # RN 依赖,通过 file:../rntpc_react-native-elements 引入本地库

5.3 本地库引入与依赖配置
在 Demo 工程的 package.json 中通过本地文件路径引入适配后的库:
"dependencies": {
"react": "19.1.1",
"react-native": "^0.82.1",
"react-native-elements": "file:../rntpc_react-native-elements",
"react-native-safe-area-context": "^5.5.2",
"react-native-vector-icons": "^10.2.0"
},
"devDependencies": {
"@react-native-oh/react-native-harmony": "^0.82.30",
"@react-native-oh/react-native-harmony-cli": "^0.82.30"
}

执行 npm install 安装依赖。适配后的库通过 file 路径引入,修改源码后无需重新发布即可在 Demo 中验证。
六、核心适配实现
6.1 平台判断 Helper
在 src/helpers/index.tsx 中新增两个统一的平台判断常量,避免在每个组件中重复写 Platform.OS 判断:
import { Platform, Dimensions } from 'react-native';
/** iOS only */
const isIOS = Platform.OS === 'ios';
/**
* OpenHarmony / HarmonyOS NEXT (RNOH).
* RNOH historically exposes Platform.OS as 'harmony' or 'ohos'.
*/
const isHarmony =
(Platform.OS as string) === 'harmony' || (Platform.OS as string) === 'ohos';
/**
* Android-like mobile styling path (Android + HarmonyOS).
* Do NOT use for TouchableNativeFeedback / Ripple — those stay Android-only.
*/
const isAndroidLike = Platform.OS === 'android' || isHarmony;
export { isIOS, isHarmony, isAndroidLike };

设计要点:isAndroidLike 用于样式路径(鸿蒙复用 Android 的视觉风格),但不能用于 TouchableNativeFeedback / Ripple——这些是 Android 独有的原生触摸反馈,鸿蒙端应回退到 TouchableOpacity。
6.2 withTheme React 19 重写
这是本次适配中最核心的修改。上游的 withTheme 使用 ThemeConsumer render-props 模式,在 React 19 下会导致主题上下文无法正确传递。重写为 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 = () => {};
/**
* OpenHarmony / React 19 compatible withTheme.
* - Uses useContext instead of ThemeConsumer render-props
* - Always wraps with forwardRef so hooks run in a real component
*/
function withTheme<P = {}, T = {}>(
WrappedComponent: React.ComponentType<P & Partial<ThemeProps<T>>>,
themeKey: string
):
| React.FunctionComponent<Omit<P, keyof ThemeProps<T>>>
| React.ForwardRefExoticComponent<P> {
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 能正确传递到被包裹的组件。
6.3 组件样式适配
Switch 组件:上游通过 isIOS 区分 iOS 和 Android 的轨道/滑块颜色逻辑。鸿蒙端复用 Android 的样式路径。修改 src/switch/Switch.tsx,将 Platform.OS 判断统一改为使用 isIOS 常量,鸿蒙自动走 Android 分支:
import { isIOS } from '../helpers';
// HarmonyOS follows Android Switch styling (not iOS).
const onTintColor = isIOS || !disabled ? switchedOnColor : theme?.colors?.disabled;
const thumbTintColor = isIOS
? undefined
: disabled || !value ? theme?.colors?.disabled : switchedOnColor;
ListItemChevron 组件:列表项的右箭头,iOS 使用 Ionicons,Android/鸿蒙使用 Material 图标:
import React from 'react';
import { StyleSheet } from 'react-native';
import { withTheme } from '../config';
import { RneFunctionComponent, isIOS } from '../helpers';
import Icon, { IconProps } from '../icons/Icon';
const ListItemChevron: RneFunctionComponent<Partial<IconProps>> = ({
containerStyle,
...props
}: Partial<IconProps>) => {
return (
<Icon
type={isIOS ? 'ionicon' : 'material'}
color="#D1D1D6"
name={isIOS ? 'chevron-forward-outline' : 'keyboard-arrow-right'}
size={16}
containerStyle={StyleSheet.flatten([
{ alignSelf: 'center' },
containerStyle,
])}
{...props}
/>
);
};
export default withTheme(ListItemChevron, 'ListItemChevron');

DialogTitle 组件:对话框标题的字重,iOS 为 500,Android/鸿蒙为 700。同样通过 isIOS 判断,鸿蒙自动走 700。
6.4 SafeAreaView 替换
上游的 Header 和 BottomSheet 组件使用了 react-native-safe-area-context 提供的 SafeAreaView,依赖原生视图 RNCSafeAreaView。在 RNOH 0.82 环境下,该原生视图不存在,会导致页面崩溃或布局异常。
适配方案:在 src/header/Header.tsx 和 src/bottomSheet/BottomSheet.tsx 中,将 SafeAreaView 的导入从 react-native-safe-area-context 改为从 react-native 核心导入:
// 修改前
import { SafeAreaView } from 'react-native-safe-area-context';
// 修改后
import { SafeAreaView } from 'react-native';
React Native 核心的 SafeAreaView 不依赖额外原生模块,在 RNOH 环境下可直接使用。功能上相比 safe-area-context 版本较少(不支持 edges 自定义等),但满足 Header 和 BottomSheet 的基本安全区需求。
6.5 其他细节修复
React 19 key 警告:ListItem 和 PadView 中通过 React.Children.map 渲染子元素时未显式指定 key。修改为将映射后的子元素包裹在带 key 的 Fragment 中:
// 修改前
{React.Children.map(children, (child) => child)}
// 修改后
{React.Children.map(children, (child, index) => (
<React.Fragment key={index}>{child}</React.Fragment>
))}
Tooltip 状态栏偏移:Tooltip 组件计算弹出位置时需要考虑状态栏高度,上游仅处理了 iOS 和 Android 的状态栏偏移 key。新增 harmony 和 ohos 的状态栏偏移 key,确保 Tooltip 在鸿蒙端弹出位置正确。
Button 触摸反馈:上游 Button 在 Android 上使用 TouchableNativeFeedback 实现水波纹效果。该组件是 Android 独有的,鸿蒙端通过 Platform.select 的 default 分支自动回退到 TouchableOpacity,无需额外修改。文档中明确说明鸿蒙端无水波纹效果,使用 TouchableOpacity 透明度反馈。
6.6 字体注册配置
react-native-elements 的 Icon 组件、CheckBox、ListItemChevron 等都依赖 react-native-vector-icons 渲染图标。在鸿蒙端,必须在 harmony/entry/src/main/ets/pages/Index.ets 的 RNApp 配置中通过 fontResourceByFontFamily 注册所需的 TTF 字体文件:
import {
AnyJSBundleProvider, MetroJSBundleProvider, RNApp,
ResourceJSBundleProvider, RNOHErrorDialog, RNOHCoreContext
} from '@rnoh/react-native-openharmony';
import { getRNOHPackages } from '../PackageProvider';
@Entry
@Component
struct Index {
@StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined;
build() {
Column() {
if (this.rnohCoreContext) {
if (this.rnohCoreContext?.isDebugModeEnabled) {
RNOHErrorDialog({ ctx: this.rnohCoreContext })
}
RNApp({
rnInstanceConfig: {
name: "RNElementsDemo",
createRNPackages: getRNOHPackages,
fontResourceByFontFamily: {
// 只注册 Demo 实际使用的字体集,控制 HAP 体积
'MaterialIcons': $rawfile('assets/fonts/MaterialIcons.ttf'),
'MaterialCommunityIcons': $rawfile('assets/fonts/MaterialCommunityIcons.ttf'),
},
enableDebugger: this.rnohCoreContext?.isDebugModeEnabled,
},
appKey: "RNElementsDemo",
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'),
})
}
}
.height('100%')
.width('100%')
}
}

注意事项:
- 只注册实际使用到的字体集,避免 HAP 包体积过大。本 Demo 使用了 MaterialIcons 和 MaterialCommunityIcons 两套字体;
- TTF 文件需放入
entry/src/main/resources/rawfile/assets/fonts/目录; - jsBundleProvider 优先使用 ResourceJSBundleProvider(本地离线 bundle),Metro 仅作为 debug 模式下的备选热更新通道,避免首启白屏。
七、Demo 示例工程
7.1 工程结构与入口
Demo 工程的入口 App.tsx 使用 ThemeProvider 包裹整个应用,将页面分为静态展示区和交互区两个独立组件,配合 FadeOverlay 动画遮罩:
import React, {useCallback, useState} from 'react';
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
View,
StatusBar,
useWindowDimensions,
} from 'react-native';
import {ThemeProvider} from 'react-native-elements';
import {StaticShowcase} from './src/components/StaticShowcase';
import {InteractivePanel} from './src/components/InteractivePanel';
import {FadeOverlay} from './src/components/FadeOverlay';
function App(): React.JSX.Element {
const [overlayVisible, setOverlayVisible] = useState(false);
const {width} = useWindowDimensions();
// MateBook / 2in1: keep content readable instead of full-bleed stretch
const contentMaxWidth = Math.min(width - 32, 560);
const openOverlay = useCallback(() => setOverlayVisible(true), []);
const closeOverlay = useCallback(() => setOverlayVisible(false), []);
return (
<ThemeProvider>
<View style={styles.root}>
<StatusBar barStyle="light-content" backgroundColor="#2089dc" />
<View style={styles.demoHeader}>
<Text style={styles.headerTitle}>React Native Elements 鸿蒙 Demo</Text>
</View>
<SafeAreaView style={styles.flex}>
<ScrollView
contentContainerStyle={styles.scroll}
removeClippedSubviews
keyboardShouldPersistTaps="handled">
<View style={[styles.content, {maxWidth: contentMaxWidth}]}>
<StaticShowcase onOpenOverlay={openOverlay} />
<InteractivePanel />
</View>
</ScrollView>
</SafeAreaView>
<FadeOverlay visible={overlayVisible} onClose={closeOverlay} />
</View>
</ThemeProvider>
);
}
const styles = StyleSheet.create({
root: {flex: 1, backgroundColor: '#f5f5f5'},
flex: {flex: 1},
scroll: {
paddingVertical: 16,
paddingBottom: 40,
alignItems: 'center',
},
content: {
width: '100%',
paddingHorizontal: 16,
},
demoHeader: {
backgroundColor: '#2089dc',
paddingVertical: 14,
paddingHorizontal: 16,
alignItems: 'center',
},
headerTitle: {color: '#fff', fontSize: 16, fontWeight: '600'},
});
export default App;

7.2 静态展示区(StaticShowcase)
展示 Button(主按钮/描边按钮)、Card(卡片 + Avatar + Badge 组合)、ListItem(带 Chevron 箭头)、Icon(带图标的按钮)。使用 React.memo 包裹,避免交互区状态变化导致静态区重渲染:
import React, { memo } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Button, Card, Avatar, Badge, ListItem, Divider, Icon } from 'react-native-elements';
function StaticShowcaseInner({ onOpenOverlay }) {
return (
<View>
<Text style={styles.section}>Button</Text>
<Button title="主按钮" onPress={() => {}} />
<Button title="次按钮" type="outline" containerStyle={styles.gap} onPress={onOpenOverlay} />
<Divider style={styles.divider} />
<Text style={styles.section}>Card / Avatar / Badge</Text>
<Card containerStyle={styles.card}>
<Card.Title>卡片标题</Card.Title>
<Card.Divider />
<View style={styles.row}>
<Avatar rounded title="鸿" containerStyle={styles.avatar} />
<View style={styles.gapLeft}>
<Text>用户昵称</Text>
<Badge value="OHOS" status="success" />
</View>
</View>
</Card>
<Divider style={styles.divider} />
<Text style={styles.section}>ListItem + Chevron</Text>
<ListItem bottomDivider onPress={() => {}}>
<ListItem.Content>
<ListItem.Title>列表项一</ListItem.Title>
<ListItem.Subtitle>带 Chevron</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
<Divider style={styles.divider} />
<Button title="打开 Overlay" icon={<Icon name="layers" color="#fff" />} onPress={onOpenOverlay} />
</View>
);
}
export const StaticShowcase = memo(StaticShowcaseInner);

7.3 交互区(InteractivePanel)
展示 Input(带左侧图标)、SearchBar(platform="default")、CheckBox(同意协议)、Switch(启用通知)、Slider(滑块,实时显示数值)。所有交互状态保持在组件内部:
import React, { memo, useCallback, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Input, SearchBar, CheckBox, Switch, Slider, Divider } from 'react-native-elements';
function InteractivePanelInner() {
const [name, setName] = useState('');
const [search, setSearch] = useState('');
const [checked, setChecked] = useState(false);
const [enabled, setEnabled] = useState(true);
const [sliderValue, setSliderValue] = useState(0.4);
const [displayValue, setDisplayValue] = useState(0.4);
const onSlidingComplete = useCallback((v) => {
setSliderValue(v);
setDisplayValue(v);
}, []);
return (
<View>
<Text style={styles.section}>Input</Text>
<Input placeholder="请输入昵称" value={name} onChangeText={setName}
leftIcon={{ type: 'material', name: 'person' }} />
<Text style={styles.section}>SearchBar (default)</Text>
<SearchBar platform="default" placeholder="搜索..." onChangeText={setSearch} value={search}
lightTheme containerStyle={styles.searchContainer} inputContainerStyle={styles.searchInput} />
<Divider style={styles.divider} />
<Text style={styles.section}>CheckBox / Switch / Slider</Text>
<CheckBox title="同意协议" checked={checked} onPress={() => setChecked(!checked)} />
<View style={styles.rowBetween}>
<Text>启用通知</Text>
<Switch value={enabled} onValueChange={setEnabled} />
</View>
<Text style={styles.sliderLabel}>滑块: {displayValue.toFixed(2)}</Text>
<Slider value={sliderValue} onValueChange={setDisplayValue} onSlidingComplete={onSlidingComplete}
maximumValue={1} minimumValue={0} thumbStyle={styles.thumb} allowTouchTrack />
</View>
);
}
export const InteractivePanel = memo(InteractivePanelInner);

7.4 动画遮罩(FadeOverlay)
演示在 RNOH 上使用 React Native 核心 Modal + Animated 实现淡入缩放效果,useNativeDriver: true 开启原生驱动:
import React, { useEffect, useRef } from 'react';
import { Animated, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
import { Button } from 'react-native-elements';
export function FadeOverlay({ visible, onClose }) {
const opacity = useRef(new Animated.Value(0)).current;
const scale = useRef(new Animated.Value(0.92)).current;
useEffect(() => {
if (visible) {
opacity.setValue(0);
scale.setValue(0.92);
Animated.parallel([
Animated.timing(opacity, { toValue: 1, duration: 220, useNativeDriver: true }),
Animated.spring(scale, { toValue: 1, friction: 7, tension: 80, useNativeDriver: true }),
]).start();
}
}, [visible, opacity, scale]);
const closeAnimated = () => {
Animated.parallel([
Animated.timing(opacity, { toValue: 0, duration: 160, useNativeDriver: true }),
Animated.timing(scale, { toValue: 0.94, duration: 160, useNativeDriver: true }),
]).start(({ finished }) => { if (finished) onClose(); });
};
return (
<Modal visible={visible} transparent animationType="none" onRequestClose={closeAnimated} statusBarTranslucent>
<View style={styles.center}>
<Pressable style={StyleSheet.absoluteFill} onPress={closeAnimated}>
<Animated.View style={[styles.backdrop, { opacity }]} />
</Pressable>
<Animated.View style={[styles.card, { opacity, transform: [{ scale }] }]}>
<Text style={styles.title}>Overlay 示例</Text>
<Text style={styles.body}>原生驱动淡入 / 缩放,点击遮罩关闭</Text>
<Button title="关闭" containerStyle={styles.gap} onPress={closeAnimated} />
</Animated.View>
</View>
</Modal>
);
}

八、构建与真机验证
8.1 编译构建
使用 DevEco Studio 打开 RNElementsDemo/harmony 目录,通过 USB 连接 MatePad Edge 真机,点击 Run 编译安装。首次编译约 5-8 分钟(包含原生 so 库编译),后续增量编译约 30 秒。


构建配置注意事项:
entry/build-profile.json5的 abiFilters 必须包含arm64-v8a(真机架构),建议同时包含x86_64(模拟器),一包两用;- 修改 abiFilters 后必须 Clean Project 再 Rebuild,清理 entry/.cxx 和 cmake/libs 中间产物,否则可能继续打出旧 ABI 包;
- 打包后可解压 HAP(本质是 zip)校验 libs/ 目录下是否存在对应架构的 librnoh_app.so。
8.2 平板模式验证
设备以平板形态使用,触摸屏交互,屏幕分辨率约 2800×1840(横屏),应用全屏运行。验证结果:
|
用例 |
操作 |
预期 |
结果 |
|
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平板展示
8.3 PC 模式验证
设备连接键盘鼠标后切换到 PC 模式,应用以窗口化方式运行,支持鼠标点击、滚轮滚动、键盘输入。验证结果:
|
用例 |
操作 |
预期 |
结果 |
|
窗口化布局 |
调整窗口大小 |
内容 maxWidth 限制生效,不过度拉伸,布局自适应 |
通过 |
|
鼠标点击 Button |
鼠标左键点击 |
点击反馈正常,onPress 触发 |
通过 |
|
物理键盘输入 |
Input/SearchBar 中用物理键盘打字 |
输入正常,光标跟随 |
通过 |
|
鼠标拖动 Slider |
鼠标按住滑块拖动 |
拖动流畅,数值实时更新 |
通过 |
|
滚轮滚动页面 |
鼠标滚轮上下滚动 |
页面滚动正常 |
通过 |
|
Modal 居中显示 |
打开 Overlay |
Modal 在窗口内居中显示,遮罩覆盖整个窗口 |
通过 |
|
CheckBox/Switch 鼠标切换 |
鼠标点击切换 |
状态切换正常 |
通过 |
演示视频:
MatePad Edge react-native电脑展示
8.4 组件支持状态
|
组件 |
状态 |
备注 |
|
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,需确认该库的鸿蒙适配状态 |
九、常见问题与解决方案
问题一:真机安装后闪退,报 libRNOHApp is undefined
现象
在 PC 模拟器上运行正常,打包安装到 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(PC 模拟器用的架构)。但 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 文件确认里面是否包含两个架构的 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 适配的入门练手。
真正花时间的是真机调试——ABI 配置不对导致闪退、Bundle 加载顺序不对导致白屏,这两个问题在模拟器上都复现不出来。所以适配完一定要上真机跑一遍,不能只在模拟器上验证。
整体来看,react-native-elements 是一个性价比很高的适配项目,难度适中,覆盖面广,做完对 RNOH 的整个开发流程会有比较完整的理解。
更多推荐




所有评论(0)