HarmonyOS NEXT API23 实战|从零开发完整版Todo待办事项应用(ArkUI声明式开发+状态管理+列表渲染全解)
一、项目前言与学习价值
在 HarmonyOS NEXT 纯血鸿蒙开发体系中,数据驱动UI是最核心的开发思想,区别于传统命令式开发,声明式UI通过状态变更自动刷新页面,极大简化交互逻辑开发。而 Todo待办事项项目,是完美衔接基础语法与业务开发的过渡项目。
很多新手学完ArkTS基础语法后,面临只会写语法、不会做项目的困境,核心原因是缺少完整的状态管理、数据处理、组件复用实战经验。
本项目基于 API23 最新规范开发,摒弃老旧兼容写法,全程企业级编码规范,帮助开发者一站式掌握:
-
结构化数据定义思想(Interface接口规范)
-
@State数组响应式状态管理核心原理
-
ArkUI常用核心组件深度使用
-
ForEach高性能列表渲染机制
-
条件渲染、文本装饰、布局适配实战
-
完整业务逻辑封装与代码解耦思维
二、项目整体介绍
2.1 项目背景
待办事项(Todo List)是全球开发者通用的入门经典项目,该项目麻雀虽小、五脏俱全,浓缩了现代前端/移动端开发的三大核心能力:数据定义与管理、用户交互监听、视图动态渲染。
在鸿蒙开发学习路径中,TodoApp是学习数据驱动UI的最佳载体,能够让开发者彻底理解:状态变、视图自动变的核心机制,为后续复杂商城、笔记、打卡、管理类应用开发打下坚实基础。
2.2 应用场景
本待办应用具备极高的实用性,日常适配多场景使用:
-
个人日常管理:记录购物清单、每日计划、学习任务、运动打卡事项
-
学习实训场景:记录课程作业、学习目标、技术学习计划
-
工作办公场景:管理日常工作任务、跟进项目进度、记录待办工作
-
习惯养成场景:记录每日阅读、健身、作息计划,辅助自律习惯养成
2.3 完整功能特性
本项目摒弃极简Demo残缺功能,实现商用级完整功能闭环:
-
新增待办任务:输入任务内容,点击按钮添加任务,自动生成唯一ID与创建时间
-
任务状态标记:通过复选框切换任务完成/未完成状态,已完成任务自动添加删除线、置灰
-
单条任务删除:精准删除指定无用任务,数据实时同步刷新
-
多维度筛选:支持全部任务、进行中任务、已完成任务三种模式切换查看
-
实时数据统计:实时展示待完成数量、总任务数、完成进度
-
批量清空功能:一键清除所有已完成任务,简化列表管理
-
空状态适配:无任务、无对应筛选数据时,友好空文案提示
2.4 页面结构分区
页面采用模块化分层设计,层级清晰、解耦性高:
-
头部统计区:展示项目标题、待完成数量、完成进度统计
-
任务输入区:文本输入框+新增按钮,实现任务录入
-
筛选切换区:标签切换三种任务筛选状态
-
任务列表区:动态渲染任务列表,自适应滚动
-
底部功能区:全局任务统计、一键清空已完成任务
2.5 开发技术栈
技术模块
详细说明
开发系统
HarmonyOS NEXT 纯血鸿蒙
API版本
API 23 最新稳定版
开发语言
ArkTS 强类型语法
UI框架
ArkUI 声明式UI
状态管理
@State 响应式状态管理
列表渲染
ForEach + List 高性能渲染
代码规范
模块化@Builder组件封装、单一职责原则
三、开发环境搭建与项目结构
3.1 新建项目流程
本次项目基于 DevEco Studio 最新版创建,适配 API23 完整规范,创建步骤如下:
-
打开 DevEco Studio,点击 Create HarmonyOS Project
-
选择 Empty Ability 空模板(纯净无冗余代码)
-
设置项目名称为 TodoApp
-
选择编译SDK版本为 API 23
-
完成创建,等待项目依赖同步完毕
3.2 标准项目结构解析
本项目遵循鸿蒙官方标准目录结构,层次清晰、便于维护拓展:
TodoApp/
├── AppScope/ # 应用全局配置目录
│ ├── app.json5 # 应用全局配置文件
│ └── resources/ # 全局静态资源
├── entry/ # 主功能模块
│ └── src/main/
│ ├── ets/ # ArkTS核心源码目录
│ │ ├── entryability/ # 应用入口Ability
│ │ └── pages/ # 页面组件目录(核心开发目录)
│ └── resources/ # 模块资源文件
├── build-profile.json5 # 项目构建配置
└── oh-package.json5 # 依赖管理配置
四、核心知识点深度精讲(高分核心干货)
本章深度拆解项目用到的10大核心知识点,从原理、语法、踩坑、实战全方位讲解,区别于普通浅层教程。
4.1 Interface 自定义数据结构(核心基础)
ArkTS基于TS强类型语法,Interface用于约束对象数据结构,规范数据格式、规避类型报错,是项目规范化开发的基础。
在Todo项目中,每一条待办任务都是一个结构化对象,包含ID、内容、状态、时间等固定字段,通过Interface统一约束。
// 待办任务数据结构接口
interface TodoItem {
id: number; // 任务唯一标识,用于精准匹配、删除、修改
text: string; // 任务文本内容
completed: boolean;// 任务完成状态布尔值
createdAt: string; // 任务创建时间
}
核心作用:强制所有任务对象格式统一,编译阶段校验数据合法性,杜绝字段缺失、类型错误等隐性Bug。
4.2 @State 数组响应式状态原理
@State是ArkUI最基础的响应式装饰器,不仅支持基础变量,完美支持数组状态监听。数组的增、删、改、筛选操作,会自动触发UI刷新,实现数据驱动视图。
// 全局状态定义
@State todos: TodoItem[] = []; // 任务数组状态
@State newTodoText: string = ''; // 输入框文本状态
@State nextId: number = 1; // 自增任务ID
@State filter: number = 0; // 筛选状态标记
重点踩坑:数组必须整体赋值更新或使用数组方法触发响应,直接修改索引值可正常渲染,是新手必须掌握的核心细节。
4.3 TextInput 输入框组件全解
TextInput是交互核心组件,用于接收用户自定义输入,支持绑定状态、监听输入变化、提交事件、焦点事件等。本项目通过TextInput实现任务内容录入。
核心要点:通过onChange实时同步输入内容到状态变量,实现双向数据绑定。
4.4 Checkbox 复选框状态交互
Checkbox用于标记任务完成状态,通过select绑定布尔值,onChange监听状态切换,实时修改数组中对应任务的completed属性,实现状态同步。
4.5 List + ListItem 高性能列表
List是鸿蒙官方推荐的滚动列表组件,相比Column循环渲染,支持按需加载、缓存优化、滚动优化,适合大量任务渲染,有效避免页面卡顿。ListItem作为列表最小渲染单元,承载单条任务UI。
4.6 ForEach 循环渲染机制
ForEach是ArkUI专用循环渲染语法,用于遍历数组批量生成组件,支持唯一Key优化渲染性能。通过唯一ID作为Key,框架可精准识别变更项,局部刷新视图,大幅提升性能。
4.7 TextDecorationType 文本装饰
通过文本装饰属性,为已完成任务添加删除线,搭配置灰字体,直观区分任务状态,提升UI层级与用户体验。
4.8 条件渲染 if/else
ArkUI支持原生if/else条件渲染,可根据任务数量、筛选状态,动态切换空状态文案与列表内容,让页面展示更人性化。
4.9 Blank 弹性填充组件
Blank组件可自动填充Row/Column剩余空间,快速实现左右布局、两端对齐、居中自适应布局,简化传统布局嵌套代码。
4.10 @Builder 模块化组件封装
通过@Builder自定义构建函数,将页面拆分为头部、输入区、筛选区、列表区、底部五大模块,代码解耦、结构清晰、便于后期维护拓展。
五、完整可运行源码(API23零报错)
路径:entry/src/main/ets/pages/Index.ets,全量替换直接运行,适配真机与模拟器。
// 定义待办任务结构化数据接口
interface TodoItem {
id: number;
text: string;
completed: boolean;
createdAt: string;
}
@Entry
@Component
struct Index {
// 全局响应式状态管理
@State todos: TodoItem[] = [];
@State newTodoText: string = '';
@State nextId: number = 1;
@State filter: number = 0; // 0-全部 1-进行中 2-已完成
build() {
Column() {
// 头部统计模块
this.HeaderSection()
// 任务输入模块
this.InputSection()
// 筛选标签模块
this.FilterSection()
// 任务列表模块
this.TodoListSection()
// 底部功能统计模块
this.FooterSection()
}
.padding(16)
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA')
}
/**
* 头部标题+统计区域
*/
@Builder HeaderSection() {
Row() {
Column() {
Text('待办事项')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
Text(`${this.todos.filter(t => !t.completed).length} 项待完成`)
.fontSize(12)
.fontColor('#6B7280')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
Blank()
// 完成进度徽章
Text(`${this.todos.filter(t => t.completed).length}/${this.todos.length}`)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#6366F1')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#E0E7FF')
.borderRadius(999)
}
.width('100%')
.padding({ bottom: 24 })
}
/**
* 任务输入区域
*/
@Builder InputSection() {
Row() {
TextInput({ placeholder: '添加新任务...', text: this.newTodoText })
.layoutWeight(1)
.height(52)
.fontSize(16)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.onChange((value: string) => {
this.newTodoText = value;
})
Button('+')
.width(52)
.height(52)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.backgroundColor('#6366F1')
.fontColor('#FFFFFF')
.borderRadius(16)
.margin({ left: 8 })
.onClick(() => {
this.addTodo();
})
}
.width('100%')
.margin({ bottom: 16 })
}
/**
* 筛选标签切换区域
*/
@Builder FilterSection() {
Row() {
Text('全部')
.fontSize(14)
.fontWeight(this.filter === 0 ? FontWeight.Medium : FontWeight.Regular)
.fontColor(this.filter === 0 ? '#6366F1' : '#6B7280')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(this.filter === 0 ? '#E0E7FF' : '#FFFFFF')
.borderRadius(8)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.onClick(() => { this.filter = 0 })
Text('进行中')
.fontSize(14)
.fontWeight(this.filter === 1 ? FontWeight.Medium : FontWeight.Regular)
.fontColor(this.filter === 1 ? '#6366F1' : '#6B7280')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(this.filter === 1 ? '#E0E7FF' : '#FFFFFF')
.borderRadius(8)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.onClick(() => { this.filter = 1 })
Text('已完成')
.fontSize(14)
.fontWeight(this.filter === 2 ? FontWeight.Medium : FontWeight.Regular)
.fontColor(this.filter === 2 ? '#6366F1' : '#6B7280')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(this.filter === 2 ? '#E0E7FF' : '#FFFFFF')
.borderRadius(8)
.layoutWeight(1)
.textAlign(TextAlign.Center)
.onClick(() => { this.filter = 2 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(4)
.margin({ bottom: 16 })
}
/**
* 任务列表区域(含空状态适配)
*/
@Builder TodoListSection() {
if (this.getFilteredTodos().length === 0) {
Column() {
Text(this.filter === 0 ? '暂无任务' : this.filter === 1 ? '没有进行中的任务' : '没有已完成的任务')
.fontSize(16)
.fontColor('#9CA3AF')
.margin({ bottom: 8 })
if (this.filter === 0) {
Text('点击上方输入框添加新任务')
.fontSize(12)
.fontColor('#9CA3AF')
}
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
List() {
ForEach(this.getFilteredTodos(), (todo: TodoItem) => {
ListItem() {
this.TodoItemComponent(todo)
}
.margin({ bottom: 8 })
}, (todo: TodoItem) => todo.id.toString())
}
.layoutWeight(1)
.width('100%')
.cachedCount(10)
.edgeEffect(EdgeEffect.Spring)
}
}
/**
* 单条任务Item组件
*/
@Builder TodoItemComponent(todo: TodoItem) {
Row() {
Checkbox()
.select(todo.completed)
.selectedColor('#6366F1')
.onChange((value: boolean) => {
const index = this.todos.findIndex(t => t.id === todo.id);
if (index >= 0) {
this.todos[index].completed = value;
}
})
Column() {
Text(todo.text)
.fontSize(16)
.fontWeight(todo.completed ? FontWeight.Regular : FontWeight.Medium)
.fontColor(todo.completed ? '#9CA3AF' : '#111827')
.decoration({
type: todo.completed ? TextDecorationType.LineThrough : TextDecorationType.None
})
Text(todo.createdAt)
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 4 })
}
.layoutWeight(1)
.margin({ left: 8 })
.alignItems(HorizontalAlign.Start)
Button('删除')
.height(32)
.fontSize(12)
.backgroundColor('#FEE2E2')
.fontColor('#EF4444')
.borderRadius(8)
.onClick(() => {
this.todos = this.todos.filter(t => t.id !== todo.id);
})
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
/**
* 底部统计与清空功能
*/
@Builder FooterSection() {
if (this.todos.length > 0) {
Row() {
Text(`共 ${this.todos.length} 项`)
.fontSize(12)
.fontColor('#9CA3AF')
Blank()
Button('清除已完成')
.fontSize(12)
.height(32)
.backgroundColor('#FEE2E2')
.fontColor('#EF4444')
.borderRadius(8)
.onClick(() => {
this.todos = this.todos.filter(t => !t.completed);
})
}
.width('100%')
.padding({ top: 16 })
}
}
/**
* 新增任务核心方法
*/
addTodo(): void {
// 去除首尾空格,拦截空任务
if (this.newTodoText.trim()) {
this.todos.push({
id: this.nextId++,
text: this.newTodoText.trim(),
completed: false,
createdAt: new Date().toLocaleDateString()
});
// 清空输入框
this.newTodoText = '';
}
}
/**
* 根据筛选条件过滤任务列表
*/
getFilteredTodos(): TodoItem[] {
switch (this.filter) {
case 1:
// 进行中:未完成任务
return this.todos.filter(t => !t.completed);
case 2:
// 已完成任务
return this.todos.filter(t => t.completed);
default:
// 全部任务
return this.todos;
}
}
}



六、代码架构深度解析
6.1 分层架构设计思想
本项目严格遵循模块化、单一职责、低耦合企业级架构,通过@Builder将页面拆分为独立功能模块,每个模块只负责自身UI渲染,核心业务逻辑单独封装方法,彻底解决新手代码堆砌、逻辑混乱的问题。
6.2 数据流转机制
采用单向数据流:用户输入触发状态变更 → 状态更新 → 视图自动刷新,完全契合鸿蒙数据驱动核心思想,逻辑清晰、BUG率极低。
6.3 容错机制设计
项目内置多重容错:拦截空任务提交、空状态友好提示、精准ID匹配修改删除、筛选逻辑闭环,极大提升项目健壮性,区别于网络极简Demo。
6.4 性能优化细节
-
List组件开启cachedCount缓存,优化长列表滚动性能
-
ForEach使用唯一ID作为Key,实现局部刷新
-
模块化组件拆分,减少无效渲染
七、高频问题与解决方案
问题1:长列表滚动卡顿
原因:默认无缓存,大量Item重复渲染
解决方案:为List添加cachedCount属性,预缓存列表项,提升滚动流畅度
问题2:空任务提交产生无效数据
原因:未拦截空字符串输入
解决方案:通过trim()去除空格,判断非空才允许新增任务
问题3:筛选逻辑混乱、数据不刷新
原因:筛选方法未复用、状态未统一管理
解决方案:封装统一筛选方法,所有列表数据均从筛选方法获取,保证数据一致性
问题4:任务状态更新不生效
原因:未精准匹配任务ID,修改错误数组项
解决方案:通过findIndex精准定位对应任务,保证状态修改精准
八、项目拓展与进阶优化方向
本项目可基于现有完整架构持续迭代,适配进阶开发学习与商用改造:
-
数据持久化:接入Preferences轻量存储,实现应用重启数据不丢失
-
任务优先级:新增高/中/低优先级标记,颜色区分展示
-
编辑功能:新增任务长按编辑、修改任务内容
-
动画优化:新增新增、删除、状态切换过渡动画
-
分类管理:新增任务分类标签,实现分类筛选
-
滑动删除:实现列表项右滑删除交互
九、全文总结
本文基于 HarmonyOS NEXT API23 最新规范,从零开发了一款功能完整、代码规范、UI精美、逻辑严谨的Todo待办事项应用。全程深度讲解Interface数据结构、@State状态管理、列表渲染、条件渲染、组件封装等核心知识点,同时解决新手高频报错与性能问题。
该项目覆盖鸿蒙ArkUI入门80%核心基础知识点,是零基础夯实鸿蒙开发能力、理解数据驱动UI思想、积累项目实战经验的标杆案例,完全满足课程实训、课程设计、毕设练手、CSDN优质原创发文需求。
更多推荐


所有评论(0)