首页 / 资讯中心 / 文章详情

React Native鸿蒙跨平台开发实践:Tag标签组件适配指南

React Native鸿蒙跨平台开发实践:Tag标签组件适配指南 ★ FEATURED ARTICLE
React Native 鸿蒙跨平台开发最近在我们团队里被重新提上了日程原因是 HarmonyOS NEXT 彻底不兼容 Android APK 之后手里这套基于 RN 的业务代码必须找到新的出路。这次我从一个看似不起眼但处处是坑的 Tag 标签组件入手把踩过的坑、梳理过的思路、磨出来的实现方案完整整理出来希望能给正在做类似鸿蒙化改造的同学一些参考。一个 Tag 标签听起来很简单无非就是一块带颜色的文字但真正把它放到 React Native 鸿蒙化的环境里你会发现它牵扯到样式差异、触摸事件、动画性能、弹层交互等一系列问题。这篇博文适合两类人看一是准备把现有 React Native 应用搬到鸿蒙上、想找切入点的人二是已经跑通基础环境、正在开发基础组件库的人。Tag 虽然小但它能帮你检验整套技术栈在鸿蒙上的完成度是一个非常理想的“试金石”组件。1. 项目背景与整体设计思路1.1 为什么要在鸿蒙生态里做 React Native 开发先说大背景。HarmonyOS NEXT 发布之后系统不再直接安装 Android APK这意味着过去“React Native 写完Android 和 iOS 双端通用”的路子在纯血鸿蒙上行不通了。我们当时手里有大量业务页面已经用 React Native 写好如果重新用 ArkTS 和 ArkUI 全部重写一遍成本实在太高。比较现实的选择是借助 OpenHarmony 社区维护的 React Native 适配层让现有 JS 代码跑在鸿蒙上。这套适配方案在社区里一般叫 RNOHReact Native on OpenHarmony。它的基本原理是在鸿蒙侧实现 React Native 的渲染层、线程模型和原生模块桥接让 JS 代码通过 HarmonyOS 的 API 完成 UI 绘制。对普通业务开发者来说大部分 JS 代码不用动真正需要花精力的是自带原生模块的组件以及那些依赖 Android 特有 UI 能力的地方。跨平台开发的收益到这里就体现出来了人力复用、逻辑复用、测试用例复用。我们有六十多个业务页面是 RN 写的如果鸿蒙端能直接跑约等于用很小的成本获得了一个新平台版本。1.2 Tag 标签组件的需求拆解Tag 标签在业务里最常见的形态是商品分类角标、用户画像标签、筛选栏选项、话题标签。它不像列表页、详情页那么复杂但正因为简单它非常适合用来验证鸿蒙适配层的完成度。我们当时把需求拆成了三块。第一是展示形态需要支持不同颜色、不同尺寸、圆角、描边、实心底色这些基础样式。第二是交互形态需要支持点击、选中态切换、禁用态、可关闭删除。第三是组合形态在筛选场景里多个 Tag 要能横向排列并支持换行。这三个需求加在一起几乎覆盖了原生 View、Text、触摸事件、状态管理、布局计算等所有 RN 基础能力一个组件写下来就能摸清鸿蒙端的水深水浅。还有个隐藏需求是主题一致性。设计团队要求 Tag 的颜色体系必须跟随应用主题走不能写死颜色值。这个看似简单的要求实际在鸿蒙端带来了不少工作量后面会重点讲。1.3 技术选型自研还是引第三方库当时我们面临一个选择直接用社区里的 RN 组件库还是自己写一个轻量 Tag。调研了一圈发现主流 RN 组件库在鸿蒙端的适配状态参差不齐尤其是涉及 StyleSheet 的兼容性和触摸事件的响应链时问题很多。另外业务里对 Tag 的需求非常定制化比如要求支持“标签点击后上报埋点”“标签可拖拽排序”第三方通用组件库很难满足。所以最终决定自研一个轻量 Tag 组件。设计原则有两条第一核心渲染层只依赖 View 和 Text不引入任何自定义原生模块这样鸿蒙端的适配风险最低第二样式和状态全部走 Props 驱动组件内部不做复杂的状态管理方便后续在多个业务模块里复用。这个决策在后面帮我们省了大量排查时间因为 Tag 组件一旦在鸿蒙端出问题我们只需要看 JS 层和基础组件的兼容性不需要查原生代码。2. 核心功能设计与 API 定义2.1 组件 API 设计用最少属性覆盖全部场景Tag 组件最终对外暴露的 API 我直接贴出来这是我们在三个业务模块里反复调优后的结果export type TagSize small | medium | large; export type TagType primary | success | warning | danger | info; export interface TagProps { label: string; size?: TagSize; type?: TagType; selected?: boolean; disabled?: boolean; closable?: boolean; onPress?: (label: string) void; onClose?: (label: string) void; containerStyle?: StylePropViewStyle; textStyle?: StylePropTextStyle; }设计这个 API 的时候我定了一条规矩凡是能通过计算推导出来的状态不额外增加布尔属性。比如“选中态”就用selected控制“禁用态”用disabled控制两者同时为 true 的时候禁用优先。很多人会把pressed也暴露出来但其实按下态应该由组件内部处理外部只需要关心点击回调。回调函数的设计也踩过一个坑。早期版本里onPress不传参UI 层要靠闭包去捕获当前 Tag 的 label结果列表重渲染时很容易绑定错数据。后来统一改成回调里带上 label让使用方通过参数判断是哪个标签被点击了出错率一下降了下来。2.2 样式体系颜色、尺寸、圆角的统一管理Tag 的样式看起来简单但最容易在鸿蒙端翻车。我们最初在 Android 上使用了 0.5 像素的边框和阴影效果到了鸿蒙上边框直接渲染模糊阴影在某些系统版本上完全不显示。后来痛定思痛把样式规范全部重做了。颜色体系现在维护在一个tagPalette对象里每种类型对应一组背景色、文字色、边框色选中时再加一套选中色禁用时统一降低透明度。这样设计的好处是业务方不需要知道具体色值只用type指定类型即可。字体大小我们做了一套字号映射比如 small 对应 12 号字medium 对应 14 号字large 对应 16 号字。这套规范跟设计团队对齐过基本能覆盖 90% 的场景。尺寸定义用的是高度和左右内边距的组合。small 高度 24medium 高度 32large 高度 40。这里有一个关键细节在鸿蒙端Text 的行高默认值跟 Android 完全不同如果只设置height而不设置lineHeight文字经常会垂直偏移甚至被裁切。我们后来统一用“高度 lineHeight 垂直 padding”的方式实现视觉居中再配合textAlignVertical: center兜底。这个坑后面在实战部分还会细说。2.3 交互事件与状态管理Tag 的点击事件我们最终选择了 Pressable 而不是 TouchableOpacity。原因是在鸿蒙适配层里TouchableOpacity 的activeOpacity效果在不同系统版本上表现不一致而 Pressable 的pressed状态可以通过渲染层自由控制适配行为更可控。按下态、选中态、禁用态三者之间需要有一个明确的优先级。我的实现顺序是disabled 优先判断其次是 selected最后才是 pressed。也就是说禁用状态下无论怎么按都不会触发选中和按压效果。关闭按钮的交互也做了特殊处理。默认关闭按钮是一个字符宽度大约 16 的 “x”但如果直接把它做成一个 Text 放在 Tag 内部点击面积会非常小用户很难点中。我们给关闭按钮外层包了一个最小 44x44 的透明触控区域这个大小是参考移动端误触规范定的虽然视觉上透明区域占了 44 像素但因为 Tag 本身就有点击范围整体体验并不会觉得奇怪。3. 鸿蒙平台适配的关键点实现3.1 环境搭建与项目工程改造要把一个现成的 React Native 工程跑上鸿蒙第一步是接入 RNOH 的构建体系。我们当时的做法是在 HarmonyOS 工程目录下通过 hvigor 配置依赖然后把 RN 的 JS 引导代码挂载到鸿蒙的原生入口上。这个过程涉及工程结构改造建议先在官方示例工程上跑通再逐步迁移业务代码。环境搭建里最容易出错的是版本匹配。RNOH 对 React Native 版本有严格对应关系我们最初引入的 RN 版本是 0.72但 RNOH 某次升级要求最低 0.73结果初始化的时候一直报 Metro 连接失败排查了整整一天才发现是版本兼容问题。后来我们固定了版本组合再升级时一定先看对应关系表。调试模式也比较特殊。鸿蒙端跑 RN 的 debug 包时需要同时保证 Metro 服务和鸿蒙调试服务在线。我们当时在 dark 模式的开发机上经常出现 Metro 掉线导致白屏后来专门做了脚本监控 Metro 进程并自动重启这个问题才算解决。3.2 Text 组件与字体渲染差异Tag 组件里最核心的元素就是 Text而它就是我们在鸿蒙端遇到的第一个大坑。鸿蒙系统默认字体是 HarmonyOS Sans它的字形和行高跟 Android 的 Roboto 有明显差异。同一个“标签”二字在 Android 上显示正常在鸿蒙上底部可能被裁掉一截或者垂直方向偏上。这个问题排查下来根因是 RN 在鸿蒙端的 Text 计算机制跟 Android 有差异。Android 上有includeFontPadding属性可以控制是否包含字体内边距但鸿蒙端该属性没有直接对应实现。我们的解决方案是给所有 Tag 的 Text 设置统一的lineHeight并配合固定的height让文字渲染区域固定不再依赖系统默认字体度量。实测这个方案在 HarmonyOS NEXT 的多个版本上都表现稳定。还有一个细节当标签文字需要省略号时Android 上设置numberOfLines{1}和ellipsizeModetail就够但鸿蒙端在某些版本上省略号不生效。我们后来发现是父容器的flexShrink没有设置导致 Text 在内部可以无限伸展省略逻辑根本触发不了。3.3 触摸事件与命中区域适配Tag 的点击和关闭操作在 Android 上都属于常规动作但在鸿蒙端触摸事件的差异非常隐蔽。有段时间用户反馈“标签点不了”后来定位到是hitSlop属性在 RNOH 的早期版本上不生效。我们当时在关闭按钮上设置了hitSlop{{ top: 12, bottom: 12, left: 8, right: 8 }}来扩大点击区域但在鸿蒙上这个属性被直接忽略了导致点击区域只有字面大小。既然系统不生效就得靠业务层自己扩容。我给关闭按钮加了一个透明的外围 View尺寸固定为 44x44里面再用一个居中小图标展示视觉内容。这样点击目标天然就是大的不依赖 hitSlop。后来 RNOH 的新版本已经支持 hitSlop 了但为了兼容还在线上运行的旧版本我们保留了这个方案。另一个触摸事件差异是onPressIn和onPressOut的触发时序。在 Android 上有完整的按下、抬起事件流但鸿蒙端如果快速滑动经过 Tag部分版本可能只触发 onPressOut 不触发 onPressIn导致按下态闪烁。因为我们的 Tag 不依赖这套时序做复杂交互直接在按下的状态计算里加了 100 毫秒的延迟过滤把这个异常跳过了。3.4 弹层与提示组件的替代方案Tag 组件在业务里经常配合提示弹层使用比如点击标签后弹出一个 Toast 或者气泡。RN 自带的 ToastAndroid 在鸿蒙端不可用我们需要自己封装一个轻提示组件。这个组件本质就是一个绝对定位的 View在屏幕顶部或底部展示几秒后自动隐藏。写的时候注意动画退出逻辑不然会残留一个透明 View 挡住点击。弹层安全区也是鸿蒙端容易忽略的地方。Android 上通常用StatusBar.currentHeight来计算顶部安全区域但鸿蒙端该 API 的返回值在不同机型上差异很大。后来我们统一走SafeAreaView并手动设置 padding 的方式在标签栏组件的布局里预留固定顶部空间总算解决了弹层贴边的问题。4. 常见问题与排查实录4.1 启动白屏不一定是代码写错了热词里有个词条是“react native 启动白屏”这个我们深有体会。鸿蒙端 RN 的启动白屏大概有三种原因。第一种是 Metro 服务连不上多见于 debug 模式报错信息里会包含 timeout 字样第二种是 JS Bundle 打包产物路径不对release 模式下找不到 bundle 文件第三种是 RNOH 版本与鸿蒙系统版本不兼容启动时原生层直接抛异常。遇到白屏先不要急着怀疑业务代码建议按照“看日志 - 看 Metro 终端 - 看 DevMenu 里的 Bundle URL”三步走。我们遇到过一个隐蔽问题release 包里的 bundle 是通过脚本在构建后临时拷贝的某次打包因为磁盘空间不足导致拷贝失败但构建流程没有报错于是产出了一个没有 bundle 的安装包典型白屏。后来在脚本里加了产物完整性校验这种问题才彻底杜绝。4.2 Tag 组件在鸿蒙上的运行表现说回这款 Tag 组件。它在 Android 上跑了两百多个业务页面一直很稳定但第一次跑鸿蒙真机时我列了一个问题清单问题现象可能原因解决建议Tag 文字垂直方向偏移鸿蒙字体行高与 Android 不同统一设置 lineHeight 和固定 height边框变为两倍粗细使用了 hairlineWidth 或 0.5 像素边框改用 1 物理像素边框关闭按钮点击无响应hitSlop 在鸿蒙端不生效用透明 View 手动扩大触控区域选中态背景色不更新快速点击时 pressed 状态冲突加 100 毫秒状态过滤标签换行排版错乱父容器 flexShrink 未设置给容器设置 flexWrap 和 flexShrink颜色偏淡shadow 属性不生效导致视觉变化改用 border 描边不依赖阴影多标签滚动卡顿列表内阴影和模糊效果叠加去除不必要的阴影样式字体图标显示为方块自定义字体未打入鸿蒙包检查字体资源是否配置到 har这个清单里的前三条几乎每个做鸿蒙 RN 开发的团队都会遇到。说到底是因为鸿蒙的渲染管线和 Android 不完全一致很多细节只有真机跑过才能暴露。4.3 动画与性能调优的降级方案业务里有几个页面用到了 Tag 的进出场动画比如选中后有一个缩放效果。在 Android 上直接用 Animated 就能实现但鸿蒙端早期版本的 RNOH 对useNativeDriver: true的支持不完整强行使用会导致动画直接不执行或者整个页面卡住。我们的调优方案是把动画拆成两类一类是简单的透明度、位移、缩放这类动画即使回退到 JS 驱动性能也还过得去另一类是复杂路径动画比如标签删除时的列表重排这类效果在鸿蒙端先用取消动画直接刷新的方式兜底等后续适配层升级后再恢复。还有一点要注意鸿蒙端对阴影和模糊的渲染成本比 Android 高不少。原来的 Tag 设计稿里有轻微的投影在 Android 上毫无压力但在鸿蒙的低端设备上出现了列表滚动掉帧。最后我们直接删掉了 Tag 的阴影样式改用浅色描边来区分层级视觉差距不大滚动流畅度提升明显。4.4 动态化与热更新的可行性很多团队在鸿蒙上跑 RN是把动态化作为核心目标的。但这里要泼一盆冷水之前 Android 端常用的 CodePush 方案在鸿蒙端并不能直接复用。RNOH 目前在动态化方面支持能力还比较基础需要自己搭建 bundle 管理服务包括版本管理、灰度发布、回滚机制。我们目前的做法是启动时从服务器拉取最新 bundle保存在鸿蒙沙盒目录里然后通过 RNOH 提供的加载接口读取本地 bundle。App 每次冷启动都会检查版本新版本下载完成后写入临时目录再做原子切换避免安装包被部分写入。当然这套机制还在持续迭代安全性上还需要补充签名校验。这套方案确实帮我们实现了不发版更新标签数据配置的功能但我不建议一上来就做太复杂的动态化体系。先让 App 能正常跑起来再逐步叠加动态更新每一步都要做充分的真机验证。5. 实操经验与后续扩展5.1 调试工具与日志排查技巧鸿蒙端的 RN 调试相比 Android 要多几步配置。我们实际常用的有三个手段第一是 DevTools 的 JS 调试可以看到 JS 层的报错和网络请求第二是鸿蒙自带的日志工具可以看到 ArkUI 渲染层和原生模块的日志第三是屏幕截图比对因为鸿蒙端暂时没有特别好用的布局查看工具我们会在测试机上来回截图对比样式差异。日志排查里有个技巧非常有价值。RN 在鸿蒙端会把 JS 报错和原生报错分成两个日志域排查问题时先看清日志前缀再决定看哪边。有一次 Tag 点击事件无响应JS 层看没有任何异常但原生日志里有一条 touch event handler not found 的警告问题一下就定位到了。如果从头到尾只看 JS 日志这个问题可能一辈子都发现不了。5.2 测试策略与多机型覆盖Tag 组件虽然简单但它牵涉的样式和交互在鸿蒙各版本上差异不小。我们的测试策略是在三台真机上做覆盖一台高分辨率旗舰机、一台中端机、一台低端机。低端机主要看滚动画面和动画性能中端机看触摸事件和字体渲染旗舰机看整体流畅度感受。另外要特别关注鸿蒙的“超大字体”模式。系统开启大字体后Tag 的 text 如果按固定高度裁剪会出现内容截断如果按自适应高度又可能把布局撑破。我们最终方案是给 Tag 设置一个最小高度同时允许文字超过最小高度时自动扩展这样正常字体下视觉统一大字模式下也不会裁字。5.3 后续扩展暗黑模式与无障碍支持Tag 组件目前已经能支持业务需求但我还在推进两个扩展方向。第一个是暗黑模式适配现在颜色体系是写死的亮色值切到暗黑模式下对比度不够。后续会把tagPalette改成动态对象根据useColorScheme返回的色板自动切换。第二个是无障碍支持。现在的 Tag 点击、关闭操作对读屏软件不友好因为鸿蒙端 RN 对无障碍属性的支持还不够完善。我们的方案是给 Tag 最外层 View 设置accessibilityLabel去掉内层 Text 的独立读屏能力避免读屏软件连续读两遍。这些细节看似不重要但在无障碍审核和特殊用户场景里是刚需。写在最后的一点体会做完这个简单又复杂的 Tag 组件我最大的感受是鸿蒙端的 React Native 生态还处于“能用但需要小心翼翼”的阶段。很多在 Android 上想当然的特性在鸿蒙上需要换个思路实现很多社区里已经默认可用的 API在这里要重新验证一遍。但也正因为这样小到一个 Tag、大到一个完整页面每一个能稳定跑通的组件都在帮整个适配层踩实地基。最后再分享一个小技巧做鸿蒙 RN 开发一定不要把 Android 的实现经验当作默认答案。遇到问题先看原生日志再在模拟器上复现最后才是查社区。组内每次出现“这个效果在Android上正常”的说法最后基本都是平台差异而非代码问题。Tag 只是一个开始但它验证了整个技术路线是走得通的这个价值比组件本身大得多。
阅读完成 · 觉得有帮助?
咨询建站