HarmonyOS应用开发实战:猫猫大作战-@Reusable 声明与复用池、复用触发条件、Reusable vs ForEach 取舍、与 Lazy
前言前面我们用ForEach渲染猫咪数组——每只猫一个CatItem子组件。但当列表项数多到几十甚至上百战绩历史、背包道具、关卡列表ForEach会全量渲染所有项哪怕屏幕只显示 5 个——性能浪费。HarmonyOS 提供了Reusable可复用组件装饰器——给子组件打上后列表滚动时离屏的项不销毁存入复用池新进入的项从池里取复用省去重建开销。本篇以「猫猫大作战」未来扩展的战绩历史列表为锚点把Reusable 声明与复用池、复用触发条件、Reusable vs ForEach 取舍、与 LazyForEach 搭配四大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–46 篇。本篇是阶段二第十七篇。一、场景拆解战绩历史长列表假设「猫猫大作战」要加战绩历史页——显示最近 100 局的得分/时间/最高连击State history: GameRecord[] []; // 100 条记录 List({ scroller: this.scroller }) { ForEach(this.history, (record: GameRecord) { ListItem() { RecordItem({ record: record }) } }, (record: GameRecord) record.id) } .cachedCount(5) // 预渲染 5 个痛点ForEach全量渲染 100 个RecordItem——屏幕只显示 8 个其余 92 个仍建组件树、占内存、耗 CPU。滚动时离屏项销毁、新项新建卡顿。Reusable 的解法Reusable Component export struct RecordItem { Prop record: GameRecord; build() { /* ... */ } } // List 检测到子组件是 Reusable自动启用复用池 List() { ForEach(this.history, (record: GameRecord) { ListItem() { RecordItem({ record: record }) } }, (record: GameRecord) record.id) } .cachedCount(5) **关键经验****Reusable 让 ListItem 离屏不销毁、入复用池新项从池取复用**——省重建开销滚动流畅。 ## 二、Reusable 声明与复用池 ### 2.1 给子组件打 Reusable ts Reusable Component export struct RecordItem { Prop record: GameRecord; build() { Row() { Text(this.record.score.toString()) .fontSize(16).fontWeight(FontWeight.Bold).fontColor(#2ECC71) Text(this.record.formatTime) .fontSize(14).fontColor(#7F8C8D) Text(x${this.record.maxCombo}) .fontSize(14).fontColor(#E74C3C) } .width(100%).height(56) .padding({ left: 16, right: 16 }) .border({ width: { bottom: 1 }, color: #ECF0F1 }) } }拆解片段含义Reusable装饰器标记「本组件可复用离屏入池」Component普通组件装饰器必须先有Prop record从父接收数据复用时重新传值关键约束Reusable 必须搭配 Component——单独 Reusable 无意义它是组件的复用标记。2.2 复用池机制List 滚动 ├─ 离屏项滚出可视区→ 不销毁存入复用池 └─ 新进入项滚入可视区→ 从池取复用重新传 Prop 值池的容量由cachedCount控制——预渲染 N 个离屏项 可视区项池容量约等于cachedCount 可视区项数。2.3 复用项的重新传值Reusable Component export struct RecordItem { Prop record: GameRecord; // 复用时重新传新 record build() { /* ... */ } } // 滚动时 // 1. 离屏的 RecordItemrecord历史第 3 局入池 // 2. 新进入的 RecordItem 从池取传 record历史第 13 局 // 3. 子组件 build() 重渲染显示第 13 局数据关键经验复用是「壳复用、数据重传」——组件实例不销毁但 Prop 重新赋值触发 build() 显示新数据。三、复用触发条件3.1 必须在 List/Grid 内// ✅ List 内触发复用 List() { ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.id) } .cachedCount(5) // ❌ 普通 Column 内不触发复用全量渲染 Column() { ForEach(this.history, (record) { RecordItem({ record: record }) }, (record) record.id) }机制只有List/Grid这类滚动容器有复用池逻辑——Column/Row是静态布局全量渲染。3.2 必须配合 ForEach 密钥List() { ForEach(this.history, (record: GameRecord) { ListItem() { RecordItem({ record: record }) } }, (record: GameRecord) record.id) // ← 密钥 }机制ForEach 用密钥做 diff配合 List 的复用池——密钥变触发 diffList 决定复用还是新建。3.3 cachedCount 预渲染List() { /* ... */ }.cachedCount(5)含义可视区外上下各预渲染 5 个——滚动时新项已就绪不闪白。cachedCount预渲染内存滚动流畅度00 个极低❌ 滚动闪白5推荐上下各 5中✅ 流畅10上下各 10高✅ 极流畅实战经验cachedCount(5)是通用平衡——内存适中滚动流畅。低端设备可降到 3。四、Reusable vs ForEach 取舍4.1 ForEach 全量渲染// 无 ReusableForEach 全量渲染所有项 ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.id) // 100 条记录 → 100 个 RecordItem 实例全占内存4.2 Reusable 池化渲染// 有 Reusable只渲染可视区 cachedCount Reusable Component export struct RecordItem { /* ... */ } List() { ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.id) }.cachedCount(5) // 100 条记录 → 只渲染可视区 8 个 cached 10 个 18 个实例4.3 对比表维度ForEach无 ReusableReusable实例数N全量可视区 cachedCount内存高N 个实例低18 个滚动销毁新建卡顿复用池流畅适合N 20少量N ≥ 20长列表必须在任意容器List/Grid关键经验N 20 用 ForEachN ≥ 20 用 Reusable List——少量列表全量渲染更简单长列表复用省内存。4.4 策策树列表项数 N ├─ N 20 → ForEach任意容器简单 └─ N ≥ 20 ├─ 静态不滚动 → ForEach全量也 OK └─ 滚动列表 → Reusable List cachedCount五、实战战绩历史列表用 Reusable5.1 定义 GameRecord 类型假设新建entry/src/main/ets/components/GameTypes.ets补充export interface GameRecord { id: string; // 唇一密钥 score: number; // 得分 formatTime: string; // 用时已格式化 maxCombo: number; // 最高连击 mergeCount: number; // 合并次数 highestLevel: CatLevel; // 最高等级 date: string; // 日期 }5.2 创建 RecordItem 子组件新建entry/src/main/ets/components/RecordItem.etsimport { GameRecord } from ./GameTypes; Reusable // ← 本篇重点标记可复用 Component export struct RecordItem { Prop record: GameRecord; build() { Row() { // 得分 Column() { Text(得分).fontSize(10).fontColor(#95A5A6) Text(this.record.score.toString()) .fontSize(18).fontWeight(FontWeight.Bold).fontColor(#2ECC71) } .alignItems(HorizontalAlign.Start) .layoutWeight(2) // 用时 Column() { Text(用时).fontSize(10).fontColor(#95A5A6) Text(this.record.formatTime) .fontSize(16).fontWeight(FontWeight.Medium).fontColor(#2C3E50) } .alignItems(HorizontalAlign.Center) .layoutWeight(2) // 最高连击 Column() { Text(连击).fontSize(10).fontColor(#95A5A6) Text(x${this.record.maxCombo}) .fontSize(16).fontWeight(FontWeight.Bold).fontColor(#E74C3C) } .alignItems(HorizontalAlign.End) .layoutWeight(2) } .width(100%).height(64) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .border({ width: { bottom: 1 }, color: #ECF0F1 }) .onClick(() { // 点击查看详情第 71 篇会专讲导航 console.info(click record, this.record.id); }) } }5.3 创建战绩历史页新建entry/src/main/ets/pages/HistoryPage.etsimport { GameRecord } from ../components/GameTypes; import { RecordItem } from ../components/RecordItem; Component export struct HistoryPage { State history: GameRecord[] []; private scroller: Scroller new Scroller(); aboutToAppear() { // 加载历史记录第 145 篇会专讲持久化 this.history this.loadHistory(); } loadHistory(): GameRecord[] { // 简化模拟 100 条记录 const records: GameRecord[] []; for (let i 0; i 100; i) { records.push({ id: record_${i}, score: 500 Math.floor(Math.random() * 2000), formatTime: ${Math.floor(Math.random() * 10)}:${Math.floor(Math.random() * 60).toString().padStart(2, 0)}, maxCombo: Math.floor(Math.random() * 10) 1, mergeCount: Math.floor(Math.random() * 50) 10, highestLevel: CatLevel.SMALL, date: 2026-07-${(20 - Math.floor(i / 10)).toString().padStart(2, 0)} }); } return records; } build() { Column() { // 标题栏 Row() { Text(战绩历史).fontSize(20).fontWeight(FontWeight.Bold).fontColor(#2C3E50) } .width(100%).height(56) .padding({ left: 16, right: 16 }) .justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Center) // 战绩列表Reusable List本篇重点 List({ scroller: this.scroller }) { ForEach(this.history, (record: GameRecord) { ListItem() { RecordItem({ record: record }) // ← Reusable 子组件 } }, (record: GameRecord) record.id) } .width(100%) .layoutWeight(1) .cachedCount(5) // ← 预渲染 5 个 .divider({ strokeWidth: 1, color: #ECF0F1, startMargin: 16, endMargin: 16 }) } .width(100%).height(100%) .backgroundColor(#FFFFFF) } }5.4 改造对比维度ForEach 全量版Reusable 版100 条实例数100 个18 个可视 8 cached 10内存~100 × RecordItem~18 × RecordItem滚动流畅卡顿销毁新建流畅复用池代码量少无装饰器略多Reusable适合N 20N ≥ 20六、Reusable 与 LazyForEach 搭配6.1 LazyForEach 按需渲染Reusable解决「复用组件实例」LazyForEach解决「按需调用数据源」——两者搭配是长列表最优解import { LazyForEach, IDataSource } from kit.ArkUI; // 数据源实现 IDataSource class HistoryDataSource implements IDataSource { private records: GameRecord[] []; totalCount(): number { return this.records.length; } getData(index: number): GameRecord { return this.records[index]; } registerDataChangeListener(listener: DataChangeListener): void { /* ... */ } unregisterDataChangeListener(listener: DataChangeListener): void { /* ... */ } reloadData(records: GameRecord[]): void { this.records records; // 通知监听器数据变 } } // 使用 private dataSource: HistoryDataSource new HistoryDataSource(); List({ scroller: this.scroller }) { LazyForEach(this.dataSource, (record: GameRecord) { ListItem() { RecordItem({ record: record }) // Reusable 子组件 } }, (record: GameRecord) record.id) } .cachedCount(5)机制LazyForEach只对可视区 cachedCount 范围的索引调getData不全量遍历 100 条。Reusable可视区外的 ListItem 入复用池新进入的复用壳。关键经验LazyForEach Reusable 是长列表最优解——前者省数据遍历后者省组件实例。本系列第 48 篇会专讲 LazyForEach。6.2 三种列表方案对比方案数据遍历组件实例适合ForEach全量 N 次全量 N 个N 20Reusable ForEach全量 N 次可视区 cachedN 20-100数据简单LazyForEach Reusable按需 18 次可视区 cachedN ≥ 100数据复杂七、踩坑提示7.1 Reusable 用在非 List/Grid// ❌ 错误Column 内用 Reusable无复用池逻辑退化为全量 Reusable Component export struct RecordItem { /* ... */ } Column() { ForEach(this.history, (record) { RecordItem({ record: record }) }, (record) record.id) } // 还是全量渲染 100 个 // ✅ 正确List 内用 List() { ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.id) }.cachedCount(5)7.2 忘 cachedCount// ❌ 错误没 cachedCount滚动时新项未预渲染闪白 List() { /* Reusable */ } // ✅ 正确设 cachedCount List() { /* Reusable */ }.cachedCount(5)7.3 Reusable 子组件用 Link// ⚠️ 复用时 Link 会重新绑定可能引起父 state 错乱 Reusable Component export struct RecordItem { Link record: GameRecord; // ❌ 不推荐 Link build() { /* ... */ } } // ✅ 推荐Reusable 子组件用 Prop只读复用时重传新值 Reusable Component export struct RecordItem { Prop record: GameRecord; // 只读复用安全 build() { /* ... */ } }关键经验Reusable 子组件用 Prop 不用 Link——复用时只读数据重传避免双向绑定错乱。7.4 ForEach 密钥不唯一// ❌ 错误密钥不唯一复用池错乱 ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.score.toString()) // 多条同得分密钥冲突 // ✅ 正确密钥唯一 ForEach(this.history, (record) { ListItem() { RecordItem({ record: record }) } }, (record) record.id) // id 唯一八、调试技巧DevEco Profiler 看组件实例数滚动时实例数应稳定在可视区cached不应随 N 增长。console.info在子组件 aboutToReuse追复用时机如果有生命周期。滚动卡顿排查检查是否在 List 内检查 cachedCount 是否设检查是否用了 LazyForEach。内存对比DevEco Memory Profiler 看 RecordItem 实例数Reusable 应远少于 N。九、性能与最佳实践N ≥ 20 的滚动列表用 Reusable——省组件实例滚动流畅。Reusable 必须在 List/Grid 内——普通容器无复用池逻辑。cachedCount(5) 平衡内存与流畅——低端设备可降到 3。Reusable 子组件用 Prop 不用 Link——复用时只读数据重传避免双向错乱。密钥必须唯一——ForEach diff 和复用池都靠密钥冲突错乱。N ≥ 100 用 LazyForEach Reusable——前者省数据遍历后者省组件实例。总结本篇我们从 Reusable 列表复用切入掌握了**Reusable 声明与复用池机制**、复用触发条件List/Grid ForEach 密钥 cachedCount、Reusable vs ForEach 取舍N 20 ForEachN ≥ 20 Reusable、与 LazyForEach 搭配四大要点并给出了战绩历史列表用 Reusable 的完整代码。核心要点Reusable 在 List 内离屏入池复用cachedCount(5) 平衡子用 Prop 不用 LinkN ≥ 100 搭 LazyForEach。下一篇我们将专讲 LazyForEach——大列表按需渲染。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源「猫猫大作战」项目源码本仓库entry/src/main/ets/components/、entry/src/main/ets/pages/ArkUI Reusable 可复用组件官方指南List 列表组件官方指南ForEach 循环渲染官方指南ArkUI 长列表性能最佳实践开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md

相关新闻

AI系统提示词精简优化:提升模型响应效果的关键策略

AI系统提示词精简优化:提升模型响应效果的关键策略

在AI模型开发和应用过程中,系统提示词的设计质量直接影响着模型的响应效果和用户体验。很多开发者习惯在系统提示词中加入大量约束条件和详细说明,试图精确控制模型行为,但过度复杂的提示词反而会干扰模型的核心能力发挥。本文将深入探讨系统…

2026/7/28 2:17:30 阅读更多 →
基于Eclipse Milo的OPC UA服务器快速搭建与工业数据采集实践

基于Eclipse Milo的OPC UA服务器快速搭建与工业数据采集实践

那天下午,我正和一位做工业自动化集成的朋友聊天。他刚接了个新项目,客户要求把产线上十几台不同品牌、不同年代的设备数据实时采集上来,统一展示。他第一反应是找硬件网关,但算下来成本直接飙到五位数,工期还拖了一个…

2026/7/28 2:17:30 阅读更多 →
Android平台OPC UA服务器开发:Eclipse Milo实现与工业自动化应用

Android平台OPC UA服务器开发:Eclipse Milo实现与工业自动化应用

在工业自动化领域,OPC(OLE for Process Control)技术扮演着至关重要的角色,它解决了不同厂商设备和软件系统之间的数据交换难题。对于初创团队而言,从项目第一天就实现盈利是一个极具挑战性的目标,而OPC技术…

2026/7/28 2:16:30 阅读更多 →

最新新闻

5步掌握Intel性能监控工具PCM:从安装到可视化监控全攻略

5步掌握Intel性能监控工具PCM:从安装到可视化监控全攻略

5步掌握Intel性能监控工具PCM:从安装到可视化监控全攻略 【免费下载链接】pcm Intel Performance Counter Monitor (Intel PCM) 项目地址: https://gitcode.com/gh_mirrors/pc/pcm Intel Performance Counter Monitor(Intel PCM)是一套…

2026/7/28 2:34:36 阅读更多 →
草图秒变艺术品:Style2Paints如何用AI颠覆你的创作流程

草图秒变艺术品:Style2Paints如何用AI颠覆你的创作流程

草图秒变艺术品:Style2Paints如何用AI颠覆你的创作流程 【免费下载链接】style2paints sketch style paints :art: (TOG2018/SIGGRAPH2018ASIA) 项目地址: https://gitcode.com/gh_mirrors/st/style2paints 你是否曾经为线稿上色感到烦恼?看着精…

2026/7/28 2:34:36 阅读更多 →
Trae AI多模型集成与第三方API中转配置指南

Trae AI多模型集成与第三方API中转配置指南

1. Trae AI多模型集成方案概述Trae AI作为新兴的AI开发工具链,其核心价值在于通过统一接口集成多个主流大语言模型。这种设计让开发者无需分别对接各家API,只需在Trae中配置一次即可灵活切换不同模型。我最近在实际项目中测试了Anthropic Claude、GPT-4o…

2026/7/28 2:34:36 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-`linearGradient` 的方向、色标、透明度、与纯色底切换的取舍

HarmonyOS应用开发实战:猫猫大作战-`linearGradient` 的方向、色标、透明度、与纯色底切换的取舍

前言 主菜单的「浅蓝渐变」背景是用户第一眼的高级感来源——从顶部 #E8F4F8 浅蓝过渡到底部 #C9E8F2 略深蓝,整屏铺色不刺眼。在 HarmonyOS 里,渐变背景靠 linearGradient(线性渐变)、sweepGradient(角度渐变&#x…

2026/7/28 2:34:36 阅读更多 →
OpenAI API结构化JSON输出实战指南

OpenAI API结构化JSON输出实战指南

1. 为什么需要JSON结构化输出在调用OpenAI API时,我们经常会遇到这样的场景:希望模型返回的数据能够直接被程序解析和处理。比如开发一个天气查询机器人,我们需要模型返回{"city":"北京","temperature":25,&qu…

2026/7/28 2:34:36 阅读更多 →
MCP Server Boot Starters:快速构建AI应用数据连接器的开发指南

MCP Server Boot Starters:快速构建AI应用数据连接器的开发指南

1. 先搞清楚 MCP 到底是什么,以及为什么需要 Server Boot Starters 如果你最近在折腾 AI 应用开发,特别是想让 Claude、GPT 这类大模型能“看到”并操作你的本地文件、数据库或者代码库,那你大概率会碰到 MCP 这个词。MCP,全称 Model Context Protocol ,你可以把它理…

2026/7/28 2:33:36 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻