一、引言
待办清单(Todo List)是前端开发领域的"Hello World",几乎所有现代前端框架的官方教程都会以它作为入门案例。这并非偶然——Todo List 虽小,却涵盖了前端开发中最核心的三大能力:
列表渲染:将数据数组映射为 UI 元素
状态管理:添加、删除、修改数据后自动更新视图
用户交互:输入框、按钮、复选框的协同工作
本文将通过构建一个完整的待办清单应用,深入讲解 ArkTS 中列表渲染与状态管理的原理与实践。
二、需求规格
2.1 功能清单
功能 描述 优先级
查看清单 显示所有待办事项列表 P0
添加事项 通过输入框和按钮添加新待办 P0
标记完成 通过复选框切换待办的完成状态 P0
删除事项 一键删除不再需要的待办 P0
完成统计 显示总数量和已完成数量 P1
2.2 数据模型
每一条待办事项需要包含以下三个字段:
interface Todo {
id: number; // 唯一标识
text: string; // 待办内容
done: boolean; // 是否完成
}
三、ArkTS 中的列表渲染
3.1 ForEach 基础用法
在 ArkTS 中,列表渲染通过 ForEach 组件实现。它的基本语法是:
ForEach(
arr: any[], // 数据源数组
itemGenerator: (item, index) => void, // 子组件生成函数
keyGenerator?: (item, index) => string // 唯一键生成函数(可选)
)
在我们的待办清单中:
List() {
ForEach(this.todos, (todo: Todo, index: number) => {
ListItem() {
// 每个待办项的 UI
}
})
}
3.2 keyGenerator 的重要性
keyGenerator 虽然标记为可选参数,但在实际开发中强烈建议提供。它的作用是帮助 ArkUI 框架在列表更新时精确追踪每个元素,实现高效的 DOM Diff。
ForEach(this.todos, (todo: Todo) => {
ListItem() { … }
}, (todo: Todo) => todo.id.toString())
如果不提供 keyGenerator,框架会使用数组索引作为默认键。这在以下场景会导致严重的渲染错误:
删除列表中间的某个元素时,后续元素会因为索引变化而重新创建
复选状态可能会错位
动画过渡异常
3.3 List 组件的特性
ArkTS 的 List 组件专为滚动列表设计,具备以下特性:
虚拟列表(Virtual List):对于长列表,只渲染可视区域内的元素,大幅提升性能
弹性滚动(Bounce Effect):在列表边界提供回弹效果
滑动操作:支持滑动删除等高级交互
在待办清单中,我们使用 List → ListItem → ForEach 的经典三层结构:
List() {
ForEach(this.todos, (todo: Todo, index: number) => {
ListItem() {
// 待办项内容
}
})
}
.layoutWeight(1) // 占据剩余空间
.width(‘100%’)
四、状态管理深入分析
4.1 @State 的反应式边界
在 ArkTS 中,@State 装饰器为变量建立了"反应式边界"(Reactive Boundary)。当 @State 变量的值发生变化时,框架会自动收集依赖于该变量的 UI 组件,并在下一个帧周期进行重新渲染。
但在操作对象数组时,有一个常见的陷阱:
错误示范:
// 直接修改数组元素属性——可能不会触发 UI 更新
let todo = this.todos[index];
todo.done = true;
正确做法:
// 通过数组索引直接修改——ArkTS 可以追踪
this.todos[index].done = true;
在 ArkTS 中, @State 对数组的监听是"浅监听"(Shallow Watch)。直接通过索引修改数组元素内部的属性是支持的(框架会追踪数组元素的变更),但替换整个数组对象也会触发更新。
4.2 添加待办
Button(‘添加’)
.onClick(() => {
let txt = this.inputText.trim();
if (txt) {
this.todos.push({ id: this.nextId++, text: txt, done: false });
this.inputText = ‘’;
}
})
这里有一个容易被忽略的细节:在添加待办之前,先对输入文本进行 trim() 操作,去除首尾空白。如果用户只输入了空格,txt 为空字符串,if (txt) 判断会阻止添加空待办。
4.3 删除待办
Button(‘🗑’)
.onClick(() => {
this.todos.splice(index, 1);
})
使用 Array.splice(index, 1) 删除指定索引的元素。splice 是原地操作(In-place Operation),会直接修改原数组。ArkTS 的 @State 能够监听到 splice 操作并触发 UI 更新。
4.4 切换完成状态
Toggle({ type: ToggleType.Checkbox, isOn: todo.done })
.onChange((v: boolean) => {
this.todos[index].done = v;
})
Toggle 组件的 onChange 回调返回当前的选中状态 v。我们将这个值直接赋给对应待办的 done 属性。
五、UI 实现细节
5.1 待办项的视觉状态
已完成和未完成的待办项在视觉上应有明显区分:
Text(todo.text)
.fontSize(16)
.decoration({
type: todo.done ? TextDecorationType.LineThrough : TextDecorationType.None
})
.fontColor(todo.done ? ‘#AAAAAA’ : ‘#333333’)
通过 decoration 属性为已完成待办添加删除线(LineThrough),同时将字体颜色变为灰色。这种视觉变化给用户提供了即时的完成反馈。
5.2 统计信息
Text(‘总计: ’ + this.todos.length)
Text(’ 已完成: ’ + this.todos.filter(t => t.done).length)
实时更新的统计信息让用户对整个清单的状态一目了然。filter 方法筛选出所有 done 为 true 的待办,再取 length 得到已完成数量。
六、常见陷阱与解决方案
6.1 列表未更新
如果修改待办后列表没有刷新,请检查:
是否使用了 @State 装饰器
是否直接修改了数组而不是替换
ForEach 是否提供了 keyGenerator
6.2 删除后状态错乱
如果在 ForEach 中没有提供 keyGenerator,删除元素后可能出现复选框状态错位的问题。这通常表现为:删除了第二项,但原来第三项的复选框被标记为"已完成"。
解决方案:始终为 ForEach 提供稳定的 keyGenerator,使用数据的唯一标识(如 todo.id)作为键。
七、扩展思路
本地持久化:使用 Preferences API 保存待办数据
分类分组:按完成状态或标签对清单进行分组
拖拽排序:使用 onDrag 和 onDrop 事件实现拖拽排序
日历集成:为待办设置截止日期
待办清单虽小,但它代表了 CRUD(Create, Read, Update, Delete)类应用的基础范式。掌握它,你就掌握了绝大多数信息管理类应用的核心模式。