# 鸿蒙 HarmonyOS 应用开发实战(第36期)|阅读清单(Reading List)— 三态流转的书单管理器
一、应用概述阅读清单Reading List是一款面向书籍爱好者的清单管理工具。用户可以添加想读的书籍书名作者并通过点击状态标签在「想读 → 在读 → 已读」三个状态之间循环切换。同时支持按状态筛选查看和删除书籍。对于经常读书的人来说管理阅读进度是一个常见需求。这款应用以极简的方式实现了书单的三态管理是 ArkTS 中状态流转、条件筛选和列表操作的经典案例。从技术层面它展示了Text作为交互按钮的巧妙用法以及concat()不可变更新在增、改、删三种操作中的一致应用。二、技术架构与组件树2.1 整体架构Entry Component struct index36 ├── Column (根容器) │ ├── Row (顶部导航栏) │ ├── Row (添加书籍输入区) │ │ ├── TextInput (书名, layoutWeight1) │ │ ├── TextInput (作者, layoutWeight1) │ │ └── Button ( 添加) │ ├── Row (状态过滤器) │ │ └── ForEach → Text × 4 (全部/想读/在读/已读) │ └── List (书籍列表, layoutWeight1) │ └── ForEach → ListItem → Row (书籍卡片) │ ├── Column │ │ ├── Text (书名) │ │ └── Text (作者) │ ├── Text (状态标签, 可点击切换) │ └── Text (✕ 删除)2.2 数据模型interface Book { id: number; title: string; author: string; status: string; // 想读 | 在读 | 已读 } State books: Book[] [ { id: 1, title: 活着, author: 余华, status: 想读 }, { id: 2, title: 三体, author: 刘慈欣, status: 在读 }, { id: 3, title: 百年孤独, author: 加西亚·马尔克斯, status: 已读 }, ]; State title: string ; State author: string ; State filter: string 全部; nextId: number 4;三个初始书籍示例涵盖了三种状态用户一打开应用就能理解使用方式。nextId作为自增 ID 生成器从 4 开始因为已有 3 本书占用了 1~3。三、核心代码深度解析3.1 添加书籍——不可变数组追加addBook(): void { if (this.title.trim() || this.author.trim() ) return; const newBooks: Book[] this.books.concat(); newBooks.push({ id: this.nextId, title: this.title, author: this.author, status: 想读 }); this.books newBooks; this.title ; this.author ; }三步操作校验检查书名和作者是否为空trim()清除首尾空格后判断不可变追加concat()创建副本 →push()添加新书 → 重新赋值清空输入添加后自动清空输入框提升连续添加的体验新书默认状态为「想读」符合直觉——刚加入书单时自然是打算读的。3.2 状态流转——三段式循环切换changeStatus(id: number): void { const newBooks: Book[] this.books.concat(); for (let i 0; i newBooks.length; i) { if (newBooks[i].id id) { const cur newBooks[i].status; let next 想读; if (cur 想读) next 在读; else if (cur 在读) next 已读; else if (cur 已读) next 想读; newBooks[i] { id: newBooks[i].id, title: newBooks[i].title, author: newBooks[i].author, status: next }; } } this.books newBooks; }三态流转图想读 ──→ 在读 ──→ 已读 ↑ │ └──────────────────┘每个状态点击后顺时针切换到下一个状态「想读」→「在读」开始阅读了「在读」→「已读」读完了「已读」→「想读」想重新读或标记错误需要修正这种循环设计比独立的「状态选择器」更简洁——用户只需反复点击即可到达任何状态无需弹出菜单或选择器。注意更新时使用了对象解构重建了一个新对象{ id, title, author, status: next }而不是直接修改newBooks[i].status。这是因为interface Book定义了不可变结构虽然直接修改属性在 JS 层面可行但保持整体替换的模式更一致、更安全。3.3 删除书籍——不可变数组过滤deleteBook(id: number): void { const newBooks: Book[] []; for (let i 0; i this.books.length; i) { if (this.books[i].id ! id) { newBooks.push(this.books[i]); } } this.books newBooks; }删除操作在不使用filter()高阶函数的情况下手动实现了过滤逻辑。遍历原数组将 ID 不匹配的元素加入新数组实现排除的效果。当然使用this.books.filter(book book.id ! id)是更简洁的写法但手动循环更易于理解底层原理。3.4 状态筛选get filteredBooks(): Book[] { if (this.filter 全部) return this.books; const result: Book[] []; for (let i 0; i this.books.length; i) { if (this.books[i].status this.filter) { result.push(this.books[i]); } } return result; }筛选逻辑很简单「全部」不过滤否则只返回状态匹配的书籍。这个计算属性在build()中被List使用当filter或books变化时自动重新计算并刷新列表。3.5 状态标签的视觉设计getStatusColor(s: string): string { if (s 想读) return #FF9800; if (s 在读) return #0077FF; return #4CAF50; }三种状态使用三种颜色编码想读橙色 #FF9800代表期待和热情在读蓝色 #0077FF代表进行中的专注已读绿色 #4CAF50代表完成的成就感Text(book.status) .fontSize(12).padding({ top: 4, bottom: 4, left: 10, right: 10 }) .backgroundColor(this.getStatusColor(book.status)) .fontColor(#fff).borderRadius(10) .onClick(() this.changeStatus(book.id))状态标签本身是一个可点击的 Text而不是 Button。点击触发changeStatus实现状态流转。这种「标签即按钮」的设计让 UI 更加紧凑减少了额外的操作元素。3.6 过滤器按钮组Row({ space: 8 }) { ForEach([全部, 想读, 在读, 已读], (f: string) { Text(f) .fontSize(13).padding({ top: 4, bottom: 4, left: 12, right: 12 }) .backgroundColor(this.filter f ? #0077FF : #eee) .fontColor(this.filter f ? #fff : #333) .borderRadius(12).onClick(() { this.filter f }) }) }过滤器按钮使用ForEach遍历四个字符串数组生成。选中状态通过背景色和文字颜色的变化来区分选中蓝色背景 白色文字未选中灰色背景 深色文字这种胶囊按钮的样式在 iOS 和 Material Design 中都很常见视觉效果简洁明快。四、HarmonyOS 特性分析4.1 List 组件的 key 生成ForEach(this.filteredBooks, (book: Book) { ListItem() { ... } }, (book: Book) book.id.toString())本例为ForEach提供了第三个参数——key 生成函数。book.id.toString()为每本书生成了唯一标识。这非常重要有 key框架知道哪个 item 是新增/删除/修改的只更新发生变化的 ListItem无 key框架可能重建整个列表性能下降且可能丢失焦点/滚动位置在列表操作增、删、改的场景中始终提供稳定的 key。4.2 TextInput 的 onSubmit 事件TextInput({ placeholder: 添加商品..., text: this.inputText }) .onSubmit(() this.addItem())虽然本例在阅读清单中没有使用onSubmit事件购物清单中使用了但值得提及——TextInput的onSubmit在用户点击键盘上的「确认/完成」键时触发。这让用户可以直接按键盘确认添加无需手动点击「」按钮提升输入效率。4.3 layoutWeight 实现弹性输入框TextInput({ placeholder: 书名, text: this.title }) .layoutWeight(1).height(40) TextInput({ placeholder: 作者, text: this.author }) .layoutWeight(1).height(40)两个TextInput都设置了layoutWeight(1)它们将等分 Row 容器中除「」按钮外的所有空间。这种弹性布局让输入框在不同屏幕宽度下都能合理分配空间。五、UI/UX 设计与最佳实践5.1 微交互设计点击状态标签流转状态标签同时是状态显示和操作入口一个点击完成状态切换添加后自动清空添加书籍后输入框清空便于连续添加红色删除按钮「✕」使用红色 (#F44336)符合删除危险的通用设计惯例5.2 筛选器位置筛选器位于输入区和列表之间形成了「输入 → 筛选 → 浏览」的自然操作流。用户先添加书籍然后可以按状态筛选查看。5.3 空状态处理当前版本没有处理 filter 筛选后无匹配项的情况。最佳实践是添加一个空状态提示if (this.filteredBooks.length 0) { Text(暂无匹配的书籍).fontSize(14).fontColor(#aaa) }5.4 数据持久化的缺失当前所有数据保存在内存中页面销毁后数据丢失。对于阅读清单这种需要长期使用的应用数据持久化是必需的。六、可扩展思路数据持久化使用ohos.data.preferences或关系型数据库RDB保存书籍列表。添加/编辑功能增强支持修改书名、作者添加书籍封面、评分、阅读笔记。排序功能按添加时间、书名、状态排序。搜索功能按书名或作者关键词搜索书籍。云同步通过云端服务在不同设备间同步书单。七、总结阅读清单应用以 130 行代码实现了一个三态流转的书籍管理器。它的技术要点包括Interface 数据建模定义 Book 接口确保数据结构一致性不可变数组操作增concat push、改concat 替换元素、删过滤构建新数组三态循环切换if/else if 链实现状态机流转ForEach key 优化为列表提供稳定的 key 以提升渲染性能条件筛选通过计算属性实现按状态过滤标签即按钮Text 组件作为交互元素减少 UI 冗余这个应用的核心代码模式——数组状态 不可变更新 ForEach 列表渲染——是 ArkTS 中 CRUD 类应用的基础架构可以广泛复用到待办事项、收藏夹、联系人管理等场景。下一期我们将继续清单类应用的主题探讨一个更贴近日常生活的工具——购物清单Shopping List看如何加入勾选标记和进度条功能。本文是 HarmonyOS 应用开发实战系列第 36 期更多内容请关注专栏。
