ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

react-native-elements 三方库鸿蒙版本适配与使用(MatePad Edge 双模式真机验证)

react-native-elements 三方库鸿蒙版本适配与使用(MatePad Edge 双模式真机验证) CPF-RN 社区地址CPF-RN - 开源代码托管,代码协作 - AtomGit上游三方库地址https://github.com/react-native-elements/react-native-elementsnpm 地址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 Native0.82.1RNOHreact-native-oh/react-native-harmony0.82.30React19.1.1DevEco Studio6.0.0.858HarmonyOS SDK6.1.1(24)runtimeOS: HarmonyOSNode.js20Metro 打包建议用 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.tsxPlatform.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 用 IoniconsAndroid/鸿蒙用 Material 图标用 isIOS 判断图标 type 和 name鸿蒙用 Material 的 keyboard-arrow-rightsrc/dialog/DialogTitle.tsx对话框标题字重iOS 为 500Android/鸿蒙为 700用 isIOS 判断 fontWeight鸿蒙自动走 700src/header/Header.tsx上游用 react-native-safe-area-context 的 SafeAreaView依赖原生视图 RNCSafeAreaViewRNOH 0.82 下不存在导致崩溃将 SafeAreaView 的导入改为从 react-native 核心导入src/bottomSheet/BottomSheet.tsx同上BottomSheet 也用了 safe-area-context 的 SafeAreaView同样改用 RN 核心 SafeAreaViewsrc/list/ListItem.tsxReact.Children.map 渲染子元素时未指定 keyReact 19 下产生 key 警告映射后的子元素包裹在带 key 的 React.Fragment 中src/tooltip/Tooltip.tsx计算弹出位置时只处理了 iOS 和 Android 的状态栏偏移 key鸿蒙端弹出位置不对新增 harmony 和 ohos 的状态栏偏移 keyharmony/entry/src/main/ets/pages/Index.etsvector-icons 的 TTF 字体在鸿蒙端不会自动加载Icon/CheckBox/Chevron 不显示jsBundleProvider 优先 Metro 会导致首启白屏fontResourceByFontFamily 注册实际使用的 TTF 字体jsBundleProvider 优先 ResourceJSBundleProviderMetro 仅作 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。2withTheme 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 forwardRefimport 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 withThemeP {}, T {}( WrappedComponent: React.ComponentTypeP PartialThemePropsT, themeKey: string ) { const name themeKey ? Themed.${themeKey} : Themed.${WrappedComponent.displayName || WrappedComponent.name || Component}; const Component WrappedComponent as React.ComponentTypeany; const Themed React.forwardRefany, 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, ...deepmergeFullTheme( (themeKey (theme[themeKey as keyof PartialFullTheme] as PartialFullTheme)) || {}, 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 能正确传递到被包裹的组件。这一个改动解决了全部组件的主题获取问题比逐个组件打补丁要干净得多。3SafeAreaView 替换上游的 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 用 IoniconsAndroid/鸿蒙用 Material 图标。DialogTitle 的字重 iOS 是 500Android/鸿蒙是 700同样用 isIOS 判断就行。5字体注册配置react-native-elements 的 Icon、CheckBox、ListItemChevron 这些组件都依赖 react-native-vector-icons 渲染图标。在鸿蒙端TTF 字体文件不会自动加载必须在 Demo 工程的 Index.ets 里通过 fontResourceByFontFamily 手动注册。注意只注册实际用到的字体集避免 HAP 包体积过大。这个 Demo 用了 MaterialIcons 和 MaterialCommunityIcons 两套字体。同时 jsBundleProvider 的顺序也要注意优先用 ResourceJSBundleProvider 读本地离线 bundleMetro 只作为 debug 模式下的备选不然首启会白屏很久这个问题在第四章详细说。6其他细节还有几个小改动ListItem 里 React.Children.map 渲染子元素时没加 keyReact 19 下会报警告给映射后的子元素包一层带 key 的 Fragment 就行。Tooltip 计算弹出位置时只处理了 iOS 和 Android 的状态栏偏移 key加上 harmony 和 ohos 的 key。Button 在 Android 上用 TouchableNativeFeedback 实现水波纹鸿蒙没有这个原生组件通过 Platform.select 的 default 分支自动回退到 TouchableOpacity不用额外改代码文档里说明一下行为差异就行。3.3 适配效果适配完成后建了一个独立的 RNOH Demo 工程来验证全部核心组件。Demo 工程分三个展示区域静态展示区StaticShowcaseButton主按钮/描边按钮、Card卡片 Avatar Badge 组合、ListItem带 Chevron 箭头、Icon带图标的按钮用 React.memo 包裹避免交互区状态变化时静态区跟着重渲染。交互区InteractivePanelInput带左侧图标、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 居中显示打开 OverlayModal 在窗口内居中显示遮罩覆盖整个窗口通过CheckBox/Switch 鼠标切换鼠标点击切换状态切换正常通过MatePad Edge react-native电脑展示组件支持状态汇总组件状态备注Button / ButtonGroup / Chip / FAB / SpeedDial支持鸿蒙使用 TouchableOpacity无 Ripple 水波纹Card / CardTitle / CardDivider / CardImage支持圆角和 elevation 阴影正常Input支持左侧图标需配置字体Avatar / Accessory支持Badge / withBadge支持SearchBar支持鸿蒙端使用 platformdefault 或 androidListItem 及全部子组件支持Chevron 使用 Material 图标CheckBox / CheckBoxIcon支持需配置 vector-icons 字体Switch支持Android 风格样式Slider支持allowTouchTrack 正常Divider支持Header支持改用 RN 核心 SafeAreaViewLinearProgress支持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 看日志核心报错是Couldnt 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_64abiFilters: [arm64-v8a, x86_64]真机手机/平板需要 arm64-v8aPC 模拟器需要 x86_64双 ABI 一包两用。2. 改完 abiFilters 后一定要 Clean 再全量编译。只改配置不清理的话cmake 可能继续用之前的缓存打出来的还是旧 ABI 的包。在 DevEco Studio 里用 Build → Clean Project然后再 Rebuild。3. 打包完成后可以解压 HAP 文件本质是 zip确认里面是否包含两个架构的 solibs/arm64-v8a/librnoh_app.so libs/x86_64/librnoh_app.so4. 确认无误后再安装到真机。小结遇到 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.jsMetroJSBundleProvider 只作为 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. 如果需要热更新调试再手动开 Metronpm start然后用 hdc 做端口反向代理hdc rport tcp:8081 tcp:8081注意 Metro 对 Node 版本有要求这个项目里用 Node 24 可以正常跑DevEco 自带的 Node 18 可能会有兼容性问题。小结启动白屏优先查 Bundle Provider 的顺序。真机演示应该默认优先本地 Resource BundleMetro 只能当开发热更新通道不能作为首启的硬依赖否则弱网或没开 Metro 时就会出现长时间白屏。五、总结这次 react-native-elements 的鸿蒙适配走下来最大的感受是纯 JS UI 库的适配门槛不高但真机调试的坑比想象中多。代码层面主要是加平台判断、处理 React 19 兼容性、替换 SafeAreaView、注册图标字体没有涉及原生模块适合作为 RNOH 适配的入门练手项目。其中 withTheme 的 React 19 重写是最核心也最隐蔽的一个改动排查花了不少时间。真正花时间的是真机调试——ABI 配置不对导致闪退、Bundle 加载顺序不对导致白屏这两个问题在 PC 模拟器上都复现不出来。所以适配完一定要上真机跑一遍不能只在模拟器上验证。整体来看react-native-elements 是一个性价比很高的适配项目难度适中覆盖面广做完对 RNOH 的整个开发流程会有比较完整的理解。
返回列表