ARTICLE DETAIL

资讯详情

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

OpenHarmony上React Native FlatList多选功能实现与性能优化

OpenHarmony上React Native FlatList多选功能实现与性能优化 近几天有朋友问我在 OpenHarmony 环境里用 React Native 做列表多选FlatList 明明写了 onPress 更新状态可界面就是纹丝不动甚至点击几次后连数据都错乱。这个问题我太熟了因为之前在适配 React Native for OpenHarmony 时踩过同款坑。今天就把从环境搭建到 FlatList 多选功能落地的完整过程拆开讲讲顺便把启动白屏、列表卡顿、批量操作这类伴随问题一并说清楚给正在折腾 OpenHarmony 侧 RN 开发的人一点参考。这个项目本身不复杂核心就是让 FlatList 的列表项支持单选/多选切换并在多选模式下完成全选、反选、批量删除或导出。难点在于 OpenHarmony 上的 React Native 适配层还不像 Android/iOS 那么成熟有些 API 行为、渲染时序、内存回收策略都不一样导致同一套写法在 Android 上没问题一到 OpenHarmony 就各种诡异。所以这篇文章不仅会给出可直接用的多选实现代码还会解释背后的状态管理逻辑和性能优化手段适合已经在 OpenHarmony 上折腾 RN、或者正准备把现有 RN 项目迁移到国产系统的开发者参考。1. 项目背景与需求拆解1.1 为什么要在 OpenHarmony 上跑 React Native先说个现实很多团队已经有了一套成熟的 React Native 业务代码不可能为了适配新系统用 ArkTS 重写一遍。OpenHarmony 作为独立开源系统需要自己的生态应用社区和厂商确实提供了 React Native 的适配层让你能把现有 JS 代码跑在鸿蒙内核上。这种方式能最大限度复用业务逻辑尤其是 FlatList 这种高频组件只要适配层稳定列表类页面几乎不用改代码。不过要注意这个“跑起来”和“跑得顺”是两码事。OpenHarmony 的 RN 运行时目前对 JavaScript 引擎、原生组件桥接、事件传递的处理还比较“年轻”我实测下来常见的问题包括首屏白屏、图片缓存失效、部分样式解析异常、FlatList 在快速滑动时出现空白项等。所以做多选之前得先把环境底子打稳否则后续调试起来全是干扰项。1.2 FlatList 多选功能的典型场景多选在移动端是标准需求文件管理器批量删除、邮箱批量标记、购物车多商品结算、聊天记录转发这些都属于 FlatList 多选范畴。在 OpenHarmony 设备上常见场景是图库/相册应用的批量操作或者文件管理器里的多选移动和上传。核心交互一般有两种直接点击某个“选择框”进入多选态再点击其他项追加选择长按列表项进入多选态然后逐个点击。无论哪种背后都离不开一套“选中状态集合”的维护以及列表项与状态集合的同步。FlatList 不同于普通 List它只渲染当前窗口内的条目所以状态如果放在组件内部没有通过额外手段传递滚出屏幕再回来很容易丢失选中态或渲染不刷新。1.3 技术选型React Native for OpenHarmony 的现状目前主流的适配方案是使用 OpenHarmony SIG 维护的 React Native 适配仓以及对应的鸿蒙化 npm 包。它会将 RN 核心组件映射到 ArkUI 的原生组件上比如 FlatList 最终可能对应的是 ScrollView Stack 或 List。因为不是官方标准 RN 组件所以版本差异很大我建议在项目开始前先锁死 RN 和 OpenHarmony 适配包的版本不然升级一次适配包FlatList 行为就可能变一次。实际操作中环境准备包括DevEco Studio 用于编译 OpenHarmony 应用壳工程Node.js 环境跑 Metro 打包服务以及 OpenHarmony SDK。初始化工程时通常会用适配仓库提供的模板然后通过 npm 安装react-native-oh/react-native-harmony这类包再在 HarmonyOS 工程里配置模块依赖。整个过程比纯 Android 开发繁琐但因为只需要配一次后面写业务代码就舒服多了。2. 环境搭建与工程初始化2.1 开发环境准备DevEco Studio Node.js这一步说多了都是眼泪。OpenHarmony 的 RN 开发不是 Android Studio 里面直接 build APK 就行它需要先构建原生壳工程再通过 Metro 加载 JS bundle。所以你本机至少要装三样东西DevEco Studio用来打开 OpenHarmony 工程编译出 HAP 包或直接装到真机Node.js 12用来跑 npm 和 MetroOpenHarmony SDKDevEco Studio 会自带但版本要跟系统匹配还有一个容易被忽略的点OpenHarmony 真机和 DevEco Studio 之间要用 hdc 工具连接。hdc 就像 Android 的 adb很多开发者习惯用 adb 连设备结果发现装不上包或者日志打不出来其实就是没装/没启 hdc。我第一次配置环境时折腾了半小时才发现是 hdc 服务没开。注意OpenHarmony 设备版本和 DevEco Studio 的 SDK 版本必须匹配否则真机跑起来很可能直接白屏或报 napi 错误。建议用官方模拟器先跑通基础流程再切真机。2.2 创建 RN 工程并接入 OpenHarmony 平台如果是从零开始最简单的做法是直接用适配模板初始化。命令大致长这样npx react-native-oh/react-native-harmony-init --projectName MultiSelectDemo --bundleName com.example.multiselect这个命令会生成一个同时包含 RN 工程和 OpenHarmony 壳工程的目录JS 代码在根目录原生工程在harmony子目录。初始化之后进入根目录执行npm install然后在 harmony 目录用 DevEco Studio 打开工程配置签名并运行到设备。如果你的项目是既有 RN 工程想接入 OpenHarmony那要在 android/ios 同级目录下手动生成 harmony 工程再把 RN 的依赖和 Metro 配置对齐。这个过程比较复杂通常建议参考适配仓库里的“已有应用接入”文档而不是手写配置。2.3 启动白屏问题与处理这个太要命了我敢说 80% 刚接触 OpenHarmony RN 的人都遇到过启动白屏。现象就是应用打开后一片白过几秒才出内容甚至一直白屏。排查思路分两层第一层是 JS bundle 有没有跑起来。在 DevEco Studio 的 Log 面板里看有没有 React Native 的日志如果只有原生日志没有 JS 日志说明 bundle 加载失败。第二层是 Metro 连不上。真机调试时RN 应用默认从电脑的 Metro 服务拉取 bundle如果手机和电脑不在同一网段或者端口没通就会白屏。我踩过的坑是OpenHarmony 上 Metro 的 localhost 指向的是设备本身而非电脑需要在 JS 入口处配置 bundle 加载地址为电脑的局域网 IP。这个跟 Android 真机调试一样但 OpenHarmony 的网络权限配置有时会导致局域网访问被拦截需要在 module.json 里检查ohos.permission.INTERNET是否已声明。3. FlatList 多选功能的核心设计3.1 多选状态的数据结构设计先说结论多选状态不要用数组用 Set。数组做全选、反选、判断是否选中都涉及遍历查找性能差且代码绕。FlatList 长列表最怕频繁遍历所以用一个Set存选中项的 id 是最理性的选择。定义在函数组件里可能是这样const [selectedKeys, setSelectedKeys] useState(new Set());这里有个 React 的老坑useState更新 Set 时如果直接调用newSet.add()React 会认为引用没变不触发重新渲染。所以每次更新都要生成一个新 Setconst toggleSelect (id) { setSelectedKeys(prev { const next new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); };这样每次返回新 Set 的引用FlatList 才能感知到状态变化。这个事看起来基础但真的有很多人写prev.add(id); return prev;然后对着白板怀疑人生。多选模式下列表可能还需要区分“编辑模式”和“普通模式”。编辑模式下点击列表项应该触发选中/取消普通模式下点击应该跳转详情。所以还需要一个isSelecting的 boolean 状态搭配selectedKeys一起使用。3.2 列表项渲染与选择交互实现FlatList 的extraData是刷新列表的钥匙。如果你只用data属性而不传extraData即使selectedKeys更新了renderItem 也不会重新调用这是 FlatList 的经典陷阱。正确写法FlatList data{listData} extraData{selectedKeys} keyExtractor{item item.id} renderItem{renderItem} /在renderItem里根据selectedKeys.has(item.id)判断是否展示选中样式。这里还有第二个坑如果你用React.memo包裹了列表项组件仅传selectedKeys本身是不够的因为memo做的是浅比较当选中状态变化时父组件重新渲染但子组件收到的isSelected是布尔值React.memo 仍可能因为其他 props 没变而跳过渲染。解决方法是把isSelected作为 prop 传入子组件并确保这个 prop 在选中态变化时一定改变。const renderItem ({ item }) ( SelectableItem item{item} isSelected{selectedKeys.has(item.id)} onPress{handlePress} / );这样每个列表项只关心自己的选中态性能更好。我遇到过一种错误说法是“把 selectedKeys 传给每个子项然后子项自己判断”。这会让所有列表项在每次选中变化时都重新渲染列表一长立刻卡顿完全不推荐。3.3 多选模式下的事件处理与防抖进入多选模式的方式有几种。常见的是长按进入然后在顶部导航栏出现“全选”“取消”按钮。这需要给 FlatList 的 item 绑定两个事件短按和长按。React Native 里可以用TouchableOpacity的onLongPress和onPress组合也可以通过Pressable实现Pressable delayLongPress{400} onPress{() handleItemPress(item)} onLongPress{() enterSelectMode(item)} 需要注意长按进入多选态时会同时触发一次onPress在handleItemPress里要做防护比如用 ref 标记当前是否处于滚动/长按状态。我习惯用isSelectingRef来同步const handleItemPress (item) { if (isSelectingRef.current) { toggleSelect(item.id); } else { openDetail(item); } };多选过程中频繁点击状态更新非常快一般不需要额外防抖因为点击是离散事件。但如果你的列表项里有“选择框”这种小按钮建议把按钮的点击区域加大否则 OpenHarmony 真机上手指稍偏移就点不中。这里的防抖不是指网络请求而是指避免在一次触摸中同时触发多次 onPress可以在onPress里判断时间戳间隔小于 300ms 的直接忽略。3.4 全选、反选与批量操作实现细节全选逻辑很简单但要考虑边界是否所有项都被选中。实现一句搞定const isAllSelected selectedKeys.size listData.length; const selectAllOrNone () { setSelectedKeys(isAllSelected ? new Set() : new Set(listData.map(item item.id))); };反选则是遍历所有项把未选中的加入新集合已选中的删除const invertSelection () { setSelectedKeys(prev { const next new Set(); listData.forEach(item { if (!prev.has(item.id)) next.add(item.id); }); return next; }); };批量删除或导出时把selectedKeys转成 array 即可。这里要提一个真实项目场景批量操作后列表数据变了选中的项可能已经不存在了一定要记得清空selectedKeys否则残留的选择项会在下次进入多选模式时幽灵般出现。我建议在数据变更请求完成后统一调用const clearSelection () setSelectedKeys(new Set());批量上传场景下通常需要把选中的文件信息传给原生模块。比如选中后点击“上传到 FTP”要通过 NativeModule 调用原生端逻辑把Array.from(selectedKeys)对应的路径传过去。这个在 OpenHarmony 适配层上有个注意点不要传 Set 对象给原生模块序列化会出问题转成普通数组最稳。4. 性能优化与用户体验4.1 FlatList 在 OpenHarmony 上的性能陷阱OpenHarmony 适配层的 FlatList 底层实现和 Android 不完全一样它对原生组件的复用策略比较保守。什么意思呢就是窗口滚出屏幕的 Item 组件可能不会立即回收导致内存占用偏高滑动久了会卡。这不是 JS 层能解决的问题但可以通过合理配置来缓解。最直接的优化是给 FlatList 设置这几个参数initialNumToRender{10} maxToRenderPerBatch{10} windowSize{5} removeClippedSubviews{Platform.OS android ? true : false}在 OpenHarmony 上removeClippedSubviews一定要谨慎开启。它在 Android 上能优化内存但在 OpenHarmony 上如果开启可能出现快速滑动时白底闪烁或内容错位的 bug。我现在的项目是关闭的靠maxToRenderPerBatch控制渲染批次来保证流畅度。另外getItemLayout对 FlatList 的性能提升极大。如果你的列表项高度固定务必写这个参数让 FlatList 不需要动态测量内容高度这对 OpenHarmony 上的滚动计算很有帮助getItemLayout{(data, index) ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, })}4.2 长列表多选的内存与滑动优化当列表项数量上千且多选时每次点击都会导致 FlatList 的视口内组件重新渲染。为了减少不必要渲染最好的办法是列表项组件用React.memo并且只把isSelected和 item 作为 props。如果 item 数据很大还可以用useCallback包裹renderItem避免父组件每次 render 都生成新的函数。更进阶的做法是把选中态存储放到一个外部 store 里比如zustand或useReducer context这样只有订阅了选中态的组件才会刷新。不过在简单场景下useState Set 已经足够。多选操作如果还需要显示“已选 5 项”这个统计信息如果每次状态变化都计算也是开销。可以在渲染层做一个记忆化const selectedCount useMemo(() selectedKeys.size, [selectedKeys]);在 OpenHarmony 开发中特别要注意控制图片类列表项的内存。列表如果包含缩略图建议用react-native-fast-image或 OpenHarmony 原生支持的图片加载方式否则多选高亮背景一旦触发底层重新绘制图片卡顿感会非常明显。4.3 选中项的视觉反馈与无障碍支持选中态不能只靠颜色区分尤其是深色模式下同一种透明度可能看不清。我做多选时会同时展示三个反馈背景颜色变化比如选中后变为主题色的 10% 透明度右上角出现勾选图标或数字序号通过布尔值控制边框宽度避免因 border 变化导致列表项高度抖动。关于高度抖动这是多选实现里最容易忽略的问题。有的团队用“显示/隐藏一个选择框”的方式控制选中态结果选择框出现时列表项高度变了FlatList 滚动跳来跳去。解决办法是选择框始终占位只是在普通模式下设为透明多选模式下才显示或者使用opacity控制视觉隐藏而不是用 conditional rendering。无障碍方面选中状态应该通过accessibilityState反馈给系统accessibilityState{{ selected: isSelected }}OpenHarmony 的无障碍服务对 RN 的兼容性还在完善中实测发现有些设备不读accessibilityState所以在视觉上做足反馈仍然是最重要的。5. 常见问题与排查实录5.1 选中状态错乱的根源状态错乱十有八九是 FlatList 复用 Item 时没有把isSelected更新到位。最常见的是在renderItem里直接引用了外层变量而不是从 item 上获取唯一 id。比如const renderItem ({ item }) { return SelectableItem isSelected{selectedKeys.has(item.id)} /; };这没问题。问题通常出现在keyExtractor缺失或返回错误 key 时。如果没有唯一 keyFlatList 会按照索引复用组件状态就会错乱。你 item 的 id 如果是纯数字keyExtractor 一定要转成字符串否则会有警告keyExtractor{item String(item.id)}全选、反选后也要注意 key 不要变化。如果列表数据重新加载时 id 顺序变化但 key 又是返回索引的那照样错乱。所以建议 data 和 keyExtractor 的 id 要保持稳定。5.2 多选滑动卡顿的调优思路排除原生适配层的问题后JS 侧最常见卡顿原因是“全量渲染”。很多人在renderItem里写了一个内联函数或者给 item 传了一个大的对象导致每次 setState 后所有可视项都重新渲染。调优路径是先用 React DevTools 或 Metro 日志定位 render 次数对列表项做React.memo用useCallback缓存 onPress如果列表项包含子列表避免非必要时重新渲染。一个很实用的排查技巧是临时在renderItem开头加console.log(render, item.id)滑动列表时如果日志刷屏说明渲染策略需要优化如果滚出屏幕再滚回来render 次数很少说明复用策略正常。OpenHarmony 还有一个特有问题是原生触摸事件和 JS 手势冲突多选模式下如果列表滑动不跟手检查是不是给 FlatList 外面套了一层ScrollView。FlatList 本身是 ScrollView 不能嵌套嵌套会导致触摸事件被拦截表现为点击响应慢或者选不中。5.3 与 FTP 等原生模块的联动要点有些需求是多选完成后要把文件传到 FTP 服务器这时候涉及 RN 和 OpenHarmony 原生模块的通信。通过 NativeModules 调用原生方法很简单但要注意两点。参数类型JS 侧传数组给原生OpenHarmony 适配层识别的是标准数组不要传带迭代器方法的数组比如 Set 转的数组没问题但自定义对象要 JSON 序列化。异步回调原生模块执行 FTP 上传时要用 Callback 或 Promise 把进度反馈给 JS。在 OpenHarmony 上官方封装一般支持 Promise但如果遇到返回结果丢失的情况多半是原生侧使用了异步线程却没有回到 JS 线程需要手动切换。这里补充一个真实场景我做过一个文件管理类的应用批量上传到 FTP 时需要先唤起多选选中项传到原生侧原生侧每次传完一个文件都用 DeviceEventEmitter 通知 JS 刷新进度。这套机制在 OpenHarmony 上事件名不能带中文或特殊字符否则会注册失败。5.4 其他兼容性问题速查表下面这个表是我做 OpenHarmony RN 开发时整理的常见问题每一条都亲身踩过现象原因解决方案启动白屏Metro 地址错误或网络权限未声明在入口配置 bundleUrl 为电脑 IP声明 INTERNET 权限FlatList 点击后滚动跳页缺少 keyExtractor 或 key 不稳定用 item.id 作为 key转字符串选中后背景不刷新未传入 extraData给 FlatList 增加 extraData{selectedKeys}Item 高度变化导致跳动使用条件渲染控制选择框改用透明占位方式隐藏快速滑动闪现白块开启 removeClippedSubviews关闭该属性原生模块返回 null回调线程错误确保原生代码回到 ArkTS UI 线程再回调5.5 与热词相关的补充真机调试白屏React Native 启动白屏如果你已经配置好 bundleUrl 仍然存在还有一个高概率原因是 DevEco Studio 的“多工程协同模式”没有开启。OpenHarmony 应用工程如果只是简单地把 harmony 目录作为独立工程打开Metro 是无法监听到 harmony 壳的。需要将 RN 根目录作为 HOS 工程的“依赖工程”协同构建然后在 DevEco Studio 中同时启动 RN 服务和 HOS 编译这样 hdc 安装的 HAP 才能找到 Metro bundle。从应用角度说纯白屏超过三秒一般就是 bundle 拉取失败建议优先看 Metro 命令行输出如果请求到了 bundle 文件且编译完成问题在原生侧如果没有任何请求就是网络或者端口问题。6. 实操总结与经验沉淀6.1 我踩过的几个大坑开发过程中最大的一个坑是过度相信 Android 上的写法能直接应用到 OpenHarmony。比如removeClippedSubviewsAndroid 上开着是性能优化在 OpenHarmony 上直接给我搞出了列表闪烁。另一个是Pressable的事件顺序长按进入多选模式后手指抬起的 onPress 仍然触发硬生生多选了一个不该选的项。我的解决方案很简单用 ref 记录isLongPressTriggered在 onPress 里判断const longPressTriggeredRef useRef(false); const handlePress (item) { if (longPressTriggeredRef.current) { longPressTriggeredRef.current false; return; } // normal press logic }; const handleLongPress (item) { longPressTriggeredRef.current true; enterSelectMode(item); toggleSelect(item.id); };另外OpenHarmony 的真机触屏采样率和 Android 不一样特别是低端设备Pressable 的onPress延迟有时候高达 100ms 以上。所以做多选时不要依赖双击等复合手势尽量用单击和长按来区分避免用户等待时间过长。6.2 后续可以继续扩展的方向多选功能完成后还可以延伸到更复杂的场景比如跨天数据分组的多选或者在 FlatList 中支持滑动批量勾选触摸滑过多个选项自动选中。后一种在 OpenHarmony 上也可以实现但需要自己在 PanResponder 中做坐标计算性能消耗比较大建议先做好节流再上。另外如果你要在 OpenHarmony 上做文件批量上传可以做一个独立的文件队列管理器用 Context 管理多选任务而不是把 FTP 上传逻辑直接写进页面组件。这样即便后续要支持断点续传或者多任务并行改动成本也会小很多。最后再分享一个小技巧做完多选之后顺手在列表数据变更逻辑里加上“自动清空选择”的副作用。因为大多数多选操作都是破坏性的删除、移动、导出完成后用户其实并不需要看到旧的选择结果。这个动作虽然简单却能明显减少复杂 bug尤其是当你把 FlatList 的data改成了过滤后的新数组时不清理selectedKeys一定会让列表项错位。从我自己的实操体验来看OpenHarmony 上的 React Native 调试周期确实比 Android 长不少但只要把 FlatList 的状态更新机制和原生适配的差异摸透多选功能写起来并不难。希望这篇从环境到坑位的梳理能让你少走几趟弯路。
返回列表