HarmonyOS ArkTS 列表渲染与数据持久化:从历史记录页面看 List 组件与存储方案选型
引子列表是应用的记忆一个完整的应用还需要记住用户做过什么。骰子应用的历史记录页面就承担了这个职责——它把每次投掷的结果保存下来让用户可以回顾。完整效果HistoryTab 的代码结构先看历史记录页面的核心代码BuilderHistoryTab(){Column(){Row(){Text(历史).fontSize(FontSize.title).fontColor(this.gc().text).fontWeight(FontWeight.Bold)Blank()if(this.history.length0){Text(清空).fontSize(FontSize.sm).fontColor(#EF4444).onClick(()this.clr())}}.width(100%).padding({left:Spacing.md,right:Spacing.md,top:Spacing.lg,bottom:Spacing.sm})if(this.history.length0){Column(){Text().fontSize(48)Text(还没有投掷记录).fontSize(FontSize.md).fontColor(this.gc().text2)}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center)}else{List(){ForEach(this.history,(r:RollResult){ListItem(){Row(){Text(DICE_TYPES.find((d:DiceType)d.keyr.diceType)?.emoji||).fontSize(22)Column(){Text((DICE_TYPES.find((d:DiceType)d.keyr.diceType)?.label||) ×String(r.results.length)).fontSize(FontSize.sm).fontColor(this.gc().text)Text([r.results.join(, )]).fontSize(FontSize.xs).fontColor(this.gc().text2).maxLines(1)}.margin({left:Spacing.sm}).alignItems(HorizontalAlign.Start)Blank()Column(){Text(String(r.total)).fontSize(FontSize.lg).fontColor(this.gc().accent).fontWeight(FontWeight.Bold)Text(this.fmt(r.rolledAt)).fontSize(FontSize.xs).fontColor(this.gc().text2)}}.width(100%).padding(Spacing.md).backgroundColor(this.gc().surface).borderRadius(BorderRadius.md).margin({bottom:Spacing.sm,left:Spacing.md,right:Spacing.md})}})}.width(100%).layoutWeight(1).scrollBar(BarState.Off)}}.width(100%).height(100%)}这段代码虽然不长但包含了几个重要的设计模式空状态处理、列表渲染、数据格式化、批量操作。空状态设计用户体验的细节当历史记录为空时页面显示一个友好的提示if(this.history.length0){Column(){Text().fontSize(48)Text(还没有投掷记录).fontSize(FontSize.md).fontColor(this.gc().text2)}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center)}空状态设计是用户体验的重要细节。很多开发者觉得空就空呗有什么好显示的但其实空状态有三个作用解释现状告诉用户这里没有数据而不是让用户猜测是不是出了 bug引导操作暗示用户去投掷几次骰子吧视觉平衡避免页面大面积空白保持视觉完整性这个应用的空状态设计还不错但可以更进一步——加一个按钮引导用户去投掷if(this.history.length0){Column(){Text().fontSize(48)Text(还没有投掷记录).fontSize(FontSize.md).fontColor(this.gc().text2).margin({top:Spacing.sm})Button(去投掷骰子).fontSize(FontSize.md).fontColor(#FFFFFF).backgroundColor(this.gc().primary).borderRadius(BorderRadius.xl).margin({top:Spacing.md}).onClick(()this.tabdice)// 切换到骰子 Tab}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center)}这样用户看到空状态后可以一键跳转到骰子页面而不是手动点击底部 Tab。列表渲染ForEach vs List代码中用了ListForEach的组合List(){ForEach(this.history,(r:RollResult){ListItem(){...}})}.width(100%).layoutWeight(1).scrollBar(BarState.Off)这里有两个关键点1. 为什么用 List 而不是 Column如果用ColumnForEach所有列表项都会被渲染即使用户只看前几项。当数据量大时这会导致初始渲染慢需要创建所有列表项的 UI 元素内存占用高所有列表项都占用内存滚动卡顿滚动时需要重新布局所有元素List组件支持懒加载只渲染用户可见的列表项。当用户滚动时List会自动创建新的列表项销毁不可见的列表项。这样无论数据量多大渲染的列表项数量都是固定的大约 10-20 个性能稳定。2. ForEach 的 key 参数ForEach的第二个参数是 item generator第三个参数是 key generator可选。代码中没有提供 key这在数据量小时没问题但当数据量大时可能导致渲染问题。比如用户删除了一条记录ForEach会重新渲染整个列表。如果没有稳定的 keyArkUI 可能会复用错误的列表项导致显示异常。建议添加 keyForEach(this.history,(r:RollResult,index:number){ListItem(){...}},(r:RollResult,index:number)index.toString())用索引作为 key 虽然不是最优解理想情况应该用唯一 ID但比没有 key 好。数据格式化时间戳的处理代码中用fmt方法格式化时间戳privatefmt(ts:number):string{constdnewDate(ts);return(d.getMonth()1)/d.getDate() String(d.getHours()).padStart(2,0):String(d.getMinutes()).padStart(2,0);}这个方法把时间戳格式化为M/D HH:MM的格式比如7/17 19:35。为什么不用 toLocaleStringJavaScript 和 ArkTS 都提供了toLocaleString()方法可以自动根据用户 locale 格式化日期。但代码没有用它原因是一致性toLocaleString()的输出格式取决于系统设置不同设备可能不一样可控性手动格式化可以精确控制输出格式性能toLocaleString()涉及 locale 计算性能略差对于这个应用手动格式化是合理的选择。但要注意getMonth()返回的月份是从 0 开始的0-11所以需要加 1。更好的时间格式化方案手动拼接字符串容易出错建议用工具函数functionformatTime(timestamp:number):string{constdnewDate(timestamp);constmonthd.getMonth()1;constdayd.getDate();consthoursString(d.getHours()).padStart(2,0);constminutesString(d.getMinutes()).padStart(2,0);return${month}/${day}${hours}:${minutes};}这样代码更清晰也更容易维护。如果将来要改格式比如加年份只需修改这一个函数。数据查找DICE_TYPES.find() 的性能代码中多次用DICE_TYPES.find()来查找骰子类型Text(DICE_TYPES.find((d:DiceType)d.keyr.diceType)?.emoji||)find()方法会遍历数组直到找到匹配的元素。对于 7 个元素的DICE_TYPES数组性能完全没问题。但如果数组很大比如几百个元素频繁调用find()可能有性能问题。优化方案是用 MapconstDICE_MAPnewMapstring,DiceType();DICE_TYPES.forEach((d:DiceType)DICE_MAP.set(d.key,d));// 使用Text(DICE_MAP.get(r.diceType)?.emoji||)Map 的查找是 O(1) 复杂度比find()的 O(n) 快很多。但在这个应用中性能差异可以忽略不计用find()就够了。数据持久化DiceDatabase 的设计DiceDatabase类封装了底层的存储逻辑。虽然看不到完整代码但从使用方式可以推断出它的接口设计classDiceDatabase{asyncinit():Promisevoid{...}add(result:RollResult):void{...}getHistory():RollResult[]{...}asyncclear():Promisevoid{...}}Preferences API vs 关系型数据库对于这种简单的键值对存储HarmonyOS 的 Preferences API 就够了。Preferences 适合存储少量配置数据API 简单性能好。但如果要支持复杂查询比如按日期筛选、统计分析建议用关系型数据库 RDB。特性Preferences关系型数据库 (RDB)数据结构键值对表、行、列查询能力按 key 获取SQL 查询性能读写快复杂查询慢适用场景配置、少量数据大量结构化数据数据上限约 1MB无硬性限制在这个应用中历史记录可能会增长到几百条。如果用 Preferences每次获取历史记录都需要读取整个数据集性能会下降。而 RDB 支持分页查询可以只获取最近的 N 条记录。数据同步策略代码中每次投掷后都会调用this.db.getHistory()重新获取历史列表if(this.db){this.db.add(createRollResult(this.diceType,fr));this.historythis.db.getHistory();}这种写后读的策略简单可靠但不是最优的。更好的方案是if(this.db){constnewResultcreateRollResult(this.diceType,fr);this.db.add(newResult);this.history[newResult,...this.history];// 直接更新状态}这样不需要重新从数据库读取直接把新结果添加到列表开头。但要注意这种方式假设add()一定成功。如果add()失败状态和数据库就不一致了。需要权衡一致性和性能。数据清空的确认代码中点击清空按钮会直接清空所有历史记录if(this.history.length0){Text(清空).fontSize(FontSize.sm).fontColor(#EF4444).onClick(()this.clr())}这很危险——用户可能误触。建议添加确认弹窗privateshowClearConfirm:booleanfalse;// 在 UI 中AlertDialog({title:确认清空,message:确定要清空所有历史记录吗此操作不可恢复。,primaryButton:{value:取消,action:(){this.showClearConfirmfalse;}},secondaryButton:{value:清空,fontColor:#EF4444,action:(){this.clr();this.showClearConfirmfalse;}}})这样用户误触时还有机会取消避免数据丢失。列表项的布局分析每个历史记录项的布局是这样的┌─────────────────────────────────┐ │ 六面骰 ×2 14 7/17│ │ [3, 11] 19:35│ └─────────────────────────────────┘左边是骰子图标和类型信息右边是总和和时间。这种左信息、右数值的布局在移动端很常见适合展示概要信息。代码用RowBlank()来实现左右分布Row(){// 左侧图标和类型信息Text(emoji).fontSize(22)Column(){...}Blank()// 占据中间空间// 右侧总和和时间Column(){...}}Blank()是 ArkUI 的弹性空白组件会占据剩余空间。这种布局方式比手动计算宽度更灵活也更容易适配不同屏幕尺寸。列表项的点击交互当前代码没有给列表项添加点击事件。如果要查看详情或删除单条记录需要添加onClickRow(){...}.width(100%).padding(Spacing.md).backgroundColor(this.gc().surface).borderRadius(BorderRadius.md).onClick(()this.showDetail(r))// 添加点击事件但要注意列表项的点击事件和List组件的滚动事件可能会冲突。如果点击响应不灵敏可能需要调整点击区域或增加长按事件。性能优化大数据量的处理当历史记录增长到几百甚至上千条时性能优化就变得重要了。几个优化方向1. 分页加载不要一次性加载所有历史记录而是分页加载Statepage:number1;StatepageSize:number20;StateallHistory:RollResult[][];privateloadMore():void{conststart(this.page-1)*this.pageSize;constendstartthis.pageSize;constnewItemsthis.db.getHistoryRange(start,end);this.allHistory[...this.allHistory,...newItems];this.page;}在List组件的onReachEnd事件中触发加载更多List(){ForEach(this.allHistory,...)}.onReachEnd(()this.loadMore())2. 虚拟列表ArkUI 的List组件默认支持虚拟列表只渲染可见区域的元素。但需要确保ListItem的高度是固定的或可预测的否则虚拟列表的效果会打折扣。3. 图片和 Emoji 的渲染代码中用了 Emoji 作为图标、 等。Emoji 的渲染比图片快但有些设备可能不支持某些 Emoji。如果要兼容更多设备建议用图片资源代替 Emoji。总结历史记录页面虽然功能简单但涉及了 ArkUI 开发的多个核心知识点列表渲染、空状态设计、数据格式化、数据持久化、性能优化。在实际开发中这些知识点会反复出现只是复杂度不同。适用边界这个页面适合用作 ArkUI 列表渲染和数据持久化的入门案例涵盖了 List 组件、ForEach 渲染、空状态设计、时间格式化等核心知识点。但如果要上架应用商店还需要补充分页加载、删除确认、详情查看、数据导出等功能。建议在此基础上逐步扩展而不是一次性做完所有功能。对于 ArkTS 新手建议从类似的小项目入手逐步理解框架的设计哲学。ArkTS 的声明式 UI 和 React 有相似之处但状态管理和生命周期有明显区别需要花时间适应。对于有经验的开发者重点是理解 ArkTS 的约束——它不是 TypeScript 的简单扩展而是一个有自己规则的框架。遵循框架的最佳实践才能写出可维护、高性能的代码。

相关新闻

CANN/Ascend C获取存储带宽API

CANN/Ascend C获取存储带宽API

GetCoreMemBw 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。 项目地址: https://gitcode.c…

2026/8/2 10:09:49 阅读更多 →
InstColorization架构详解:理解融合模块与实例分支的设计原理

InstColorization架构详解:理解融合模块与实例分支的设计原理

InstColorization架构详解:理解融合模块与实例分支的设计原理 【免费下载链接】InstColorization 项目地址: https://gitcode.com/gh_mirrors/in/InstColorization InstColorization是一个基于深度学习的实例感知图像着色系统,它通过创新的融合模…

2026/8/2 10:09:49 阅读更多 →
零基础构建知识图谱:3步实现非结构化数据到Neo4j的智能转换

零基础构建知识图谱:3步实现非结构化数据到Neo4j的智能转换

零基础构建知识图谱:3步实现非结构化数据到Neo4j的智能转换 【免费下载链接】llm-graph-builder Neo4j graph construction from unstructured data using LLMs 项目地址: https://gitcode.com/GitHub_Trending/ll/llm-graph-builder 想要将海量文档、网页和…

2026/7/30 9:15:58 阅读更多 →

最新新闻

Remio:为AI工具打造持续工作记忆,告别重复对话

Remio:为AI工具打造持续工作记忆,告别重复对话

最近在折腾各种 AI 工具时,我遇到了一个非常具体且恼人的问题:每次和 Claude、GPT 或者本地部署的模型对话,聊到项目细节、代码片段或者某个特定偏好时,都得从头解释一遍。昨天刚告诉它我的项目结构,今天再问个新问题&…

2026/8/2 10:09:27 阅读更多 →
I2C LCD驱动全解析:从协议原理到多平台实战与故障排查

I2C LCD驱动全解析:从协议原理到多平台实战与故障排查

1. 项目概述:I2C LCD的入门与精要如果你玩过单片机,尤其是像Arduino、STM32或者树莓派Pico这类开发板,大概率会接触过一种叫“LCD1602”或“LCD2004”的小屏幕。它们能显示两行或四行字符,是调试信息、状态显示的神器。但传统的并…

2026/8/2 10:09:27 阅读更多 →
AI绘画中逗号分隔问题的解决方案

AI绘画中逗号分隔问题的解决方案

1. 问题背景与核心痛点在AI绘画Web UI中使用自然语言描述生成图像时,许多用户发现一个令人困扰的现象:当输入描述中包含逗号时,系统会自动将逗号识别为标签分隔符(tag separator)。这导致原本流畅的自然语言描述被强行…

2026/8/2 10:09:27 阅读更多 →
Wazuh部署实战:Ubuntu 22.04上构建开源SIEM/XDR平台

Wazuh部署实战:Ubuntu 22.04上构建开源SIEM/XDR平台

1. 为什么现在部署Wazuh需要一份“超详细”指南?如果你最近在搜索Wazuh的安装教程,可能会发现一个现象:很多一两年前的“保姆级”教程,照着做大概率会卡在某个环节,不是依赖包版本冲突,就是配置文件格式对不…

2026/8/2 10:09:27 阅读更多 →
企业微信API报错60020排查指南:IP白名单配置与网络架构实战

企业微信API报错60020排查指南:IP白名单配置与网络架构实战

1. 项目概述:当企业微信通讯录同步“罢工”时 在企业应用集成和自动化流程中,企业微信的通讯录同步API扮演着至关重要的角色。无论是将HR系统的新员工信息自动推送到企业微信,还是将企业微信的组织架构拉取到内部CRM进行权限映射,…

2026/8/2 10:09:27 阅读更多 →
网盘直链下载助手:终极免费提速方案,8大网盘下载速度提升10倍!

网盘直链下载助手:终极免费提速方案,8大网盘下载速度提升10倍!

网盘直链下载助手:终极免费提速方案,8大网盘下载速度提升10倍! 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / …

2026/8/2 10:08:27 阅读更多 →

日新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/2 6:34:16 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/2 2:47:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/2 0:23:22 阅读更多 →