HarmonyOS NEXT API23 实战|从零开发完整版Todo待办事项应用(ArkUI声明式UI+状态管理全解析)
一、项目概述
1.1 项目背景
在 HarmonyOS NEXT 纯血鸿蒙开发体系中,数据驱动UI是核心开发思想,区别于传统命令式开发,声明式UI通过状态变更自动刷新页面,极大简化交互逻辑与页面渲染流程。
Todo待办事项项目作为全球开发者通用入门项目,麻雀虽小五脏俱全,浓缩了移动端开发的核心逻辑:结构化数据定义、用户交互监听、数据增删改查、视图动态渲染、状态实时同步,是新手从语法学习过渡到项目实战的最佳载体,为后续复杂应用开发夯实基础。
1.2 应用场景
本待办应用实用性极强,适配多场景日常使用:
-
个人生活管理:记录购物清单、每日计划、运动、阅读等日常事项
-
学习任务管理:整理课程作业、技术学习计划、考证备考任务
-
职场工作管理:统计日常工作、跟进项目任务、梳理待办工作清单
-
自律习惯养成:记录每日打卡任务,辅助养成良好作息与学习习惯
1.3 核心功能特性
摒弃残缺Demo功能,本项目实现全闭环商用级基础功能:
-
新增待办任务:输入任务内容,点击按钮提交,自动生成唯一ID与创建时间,拦截空任务提交
-
任务状态切换:复选框一键标记任务完成/进行中,已完成任务自动置灰+添加删除线
-
单任务删除:精准删除指定无用任务,数据实时同步,视图自动刷新
-
多维度筛选:支持全部任务、进行中任务、已完成任务三种模式无缝切换
-
实时数据统计:动态展示待完成数量、总任务数、完成进度
-
批量清理功能:一键清除所有已完成任务,简化列表管理
-
空状态适配:无任务、无对应筛选数据时,展示友好提示文案,提升用户体验
1.4 页面结构分区
项目采用模块化分层设计,页面层级清晰、解耦性高、便于迭代优化:
-
头部统计区:展示应用标题、待完成任务数量、整体完成进度
-
任务输入区:文本输入框+新增按钮,实现任务快速录入
-
筛选切换区:标签切换三种任务筛选状态,交互直观
-
任务列表区:动态渲染任务列表,支持弹性滚动、性能优化
-
底部功能区:全局任务统计、一键清空已完成任务功能
1.5 技术栈详解
技术模块
详细说明
开发系统
HarmonyOS NEXT 纯血鸿蒙
API版本
API 23 最新稳定版
开发语言
ArkTS 强类型语法
UI框架
ArkUI 声明式UI
状态管理
@State 响应式状态管理
列表渲染
ForEach + List 高性能渲染
代码规范
@Builder 模块化组件拆分、单一职责设计
二、开发环境搭建与项目结构
2.1 项目创建步骤
基于 DevEco Studio 最新版创建纯净项目,适配 API23 规范,步骤如下:
-
打开 DevEco Studio,点击 Create HarmonyOS Project 创建新项目
-
选择官方纯净模板 Empty Ability,无冗余代码,适合新手开发
-
自定义项目名称为 TodoApp
-
编译SDK版本选择 API 23
-
等待项目依赖自动同步,完成初始化
2.2 标准项目目录结构
本项目遵循鸿蒙官方标准目录规范,层级清晰、易于维护拓展:
TodoApp/
├── AppScope/ # 应用全局配置目录
│ ├── app.json5 # 应用全局配置文件
│ └── resources/ # 全局静态资源
├── entry/ # 主功能模块
│ └── src/main/
│ ├── ets/ # ArkTS核心源码目录
│ │ ├── entryability/ # 应用入口Ability
│ │ └── pages/ # 页面组件目录(核心开发目录)
│ └── resources/ # 模块静态资源
├── build-profile.json5 # 项目构建配置
└── oh-package.json5 # 依赖管理配置
三、核心知识点深度精讲
本章深度拆解项目用到的10大核心知识点,从语法原理+实战用法+踩坑要点三维讲解,告别只会抄代码不懂原理的问题。
3.1 Interface 强类型数据接口
ArkTS基于TS强类型语法,Interface用于约束对象数据结构,统一数据格式,在编译阶段校验数据合法性,规避字段缺失、类型错误等隐性Bug,是规范化开发的基础。
本项目通过Interface定义单条待办任务的完整结构,统一所有任务数据格式:
interface TodoItem {
id: number; // 任务唯一标识,用于精准增删改查
text: string; // 任务文本内容
completed: boolean;// 任务完成状态
createdAt: string; // 任务创建时间
}
3.2 @State 数组响应式状态管理
@State是ArkUI最基础的响应式装饰器,不仅支持基础变量,完美适配数组类型状态。数组的增、删、改、筛选操作会自动触发UI刷新,实现数据驱动视图的核心效果。
核心状态定义,统一管理页面所有动态数据:
@State todos: TodoItem[] = []; // 待办任务数组
@State newTodoText: string = ''; // 输入框绑定文本
@State nextId: number = 1; // 任务自增唯一ID
@State filter: number = 0; // 筛选标记:0-全部 1-进行中 2-已完成
核心踩坑点:直接修改数组索引可正常触发响应,搭配数组方法可实现完整的数据更新,无需手动刷新页面。
3.3 TextInput 文本输入组件
TextInput是核心交互组件,用于接收用户自定义输入,支持双向数据绑定、输入监听、提交事件等,本项目通过该组件实现任务内容录入。通过onChange实时同步输入内容到状态变量,实现输入内容实时联动。
3.4 Checkbox 复选框交互组件
Checkbox用于标记任务完成状态,通过select绑定任务布尔值状态,onChange监听状态切换,精准修改对应任务的completed属性,实现状态同步更新,搭配文本装饰实现视觉差异化。
3.5 List + ListItem 高性能列表
List是鸿蒙官方推荐的滚动列表组件,相比Column静态渲染,支持按需加载、缓存优化、弹性滚动,适合大量数据渲染,有效避免页面卡顿。ListItem作为最小渲染单元,承载单条任务UI布局。
3.6 ForEach 高性能循环渲染
ForEach是ArkUI专用循环渲染语法,用于遍历数组批量生成组件。通过唯一ID作为key,帮助框架精准识别变更项,实现局部刷新,大幅提升列表渲染性能,是列表开发的核心语法。
3.7 TextDecorationType 文本装饰
通过文本装饰属性,为已完成任务添加删除线,同时搭配字体置灰,直观区分任务状态,优化页面层级与用户视觉体验。
3.8 条件渲染 if/else
ArkUI支持原生if/else条件渲染,可根据任务数量、筛选状态动态切换页面内容,实现空状态适配、列表动态展示,让页面交互更人性化。
3.9 Blank 弹性填充组件
Blank组件可自动填充Row/Column剩余空白空间,快速实现两端对齐、居中自适应布局,简化传统布局嵌套冗余代码,让页面适配性更强。
3.10 @Builder 模块化组件封装
通过@Builder自定义构建函数,将页面拆分为多个独立功能模块,实现代码解耦、单一职责、复用性提升,彻底解决新手代码堆砌、逻辑混乱的问题。
四、完整可运行源码(零报错、直接部署)
路径:entry/src/main/ets/pages/Index.ets,全量替换即可运行,兼容模拟器与真机,适配API23所有特性。
// 定义待办任务结构化数据接口
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;
}
}
}



五、代码架构深度解析
5.1 模块化架构设计
本项目严格遵循单一职责、低耦合、高复用的企业级开发思想,通过@Builder将页面拆分为5大独立功能模块,每个模块仅负责自身UI渲染,核心业务逻辑单独封装工具方法,彻底避免代码堆砌、逻辑混乱的问题,便于后期迭代维护。
5.2 单向数据流机制
项目采用标准数据驱动流程:用户操作触发状态变更 → 状态实时更新 → 视图自动刷新,完全契合鸿蒙声明式UI核心思想,数据流转清晰、BUG率极低,逻辑闭环完整。
5.3 容错机制设计
内置多重容错逻辑,提升项目健壮性:拦截空任务提交、多场景空状态适配、通过唯一ID精准匹配修改/删除任务、筛选逻辑全覆盖,规避新手开发常见的逻辑漏洞。
5.4 性能优化细节
-
List组件开启cachedCount缓存,优化长列表滚动卡顿问题
-
ForEach使用唯一ID作为Key,实现列表局部刷新,减少无效渲染
-
模块化组件拆分,按需渲染,提升页面加载效率
六、高频问题与解决方案
问题1:长列表滚动卡顿
问题原因:默认无列表缓存,大量列表项重复渲染,消耗性能
解决方案:为List组件添加cachedCount属性,预缓存指定数量列表项,大幅提升滚动流畅度
问题2:空任务可重复提交
问题原因:未对输入内容做非空校验,空格内容可正常提交
解决方案:通过trim()去除首尾空格,校验非空后再执行新增逻辑,拦截无效数据
问题3:筛选后数据不刷新
问题原因:筛选逻辑分散,未统一复用筛选方法
解决方案:封装全局统一筛选方法,所有列表数据均从该方法获取,保证数据一致性
问题4:应用重启数据丢失
问题原因:当前为内存级状态,无本地持久化存储
解决方案:可接入Preferences轻量存储,实现数据本地持久化,重启不丢失
七、项目进阶拓展方向
本项目架构可无缝迭代,适合进阶学习拓展,可新增以下功能:
-
数据持久化:接入Preferences/RDB数据库,实现任务数据永久保存
-
任务优先级:新增高/中/低优先级标记,颜色差异化展示
-
任务编辑功能:长按任务实现内容修改、重新编辑
-
动画优化:新增任务新增、删除、状态切换过渡动画
-
滑动删除:实现列表项右滑删除交互,优化操作体验
-
分类管理:新增任务分类标签,实现多维度分类筛选
八、项目总结
本文基于 HarmonyOS NEXT API23 最新规范,从零开发了一款功能完整、代码规范、UI精美的Todo待办事项应用。全程覆盖Interface数据结构定义、@State响应式状态管理、ArkUI核心组件、ForEach循环渲染、条件渲染、模块化组件封装等鸿蒙入门核心刚需知识点。
该项目完美诠释了鸿蒙数据驱动UI的开发思想,代码结构清晰、逻辑严谨、可扩展性强,零基础可快速上手,非常适合作为鸿蒙入门实训、课程设计、毕设练手、技术发文的标杆项目,助力开发者夯实ArkUI声明式开发基础。
更多推荐


所有评论(0)