一、项目前言与学习价值
在 HarmonyOS NEXT 纯血鸿蒙开发体系中,数据驱动UI是最核心的开发思想,区别于传统命令式开发,声明式UI通过状态变更自动刷新页面,极大简化交互逻辑开发。而 Todo待办事项项目,是完美衔接基础语法与业务开发的过渡项目。

很多新手学完ArkTS基础语法后,面临只会写语法、不会做项目的困境,核心原因是缺少完整的状态管理、数据处理、组件复用实战经验。

本项目基于 API23 最新规范开发,摒弃老旧兼容写法,全程企业级编码规范,帮助开发者一站式掌握:

  • 结构化数据定义思想(Interface接口规范)

  • @State数组响应式状态管理核心原理

  • ArkUI常用核心组件深度使用

  • ForEach高性能列表渲染机制

  • 条件渲染、文本装饰、布局适配实战

  • 完整业务逻辑封装与代码解耦思维

二、项目整体介绍

2.1 项目背景

待办事项(Todo List)是全球开发者通用的入门经典项目,该项目麻雀虽小、五脏俱全,浓缩了现代前端/移动端开发的三大核心能力:数据定义与管理、用户交互监听、视图动态渲染。

在鸿蒙开发学习路径中,TodoApp是学习数据驱动UI的最佳载体,能够让开发者彻底理解:状态变、视图自动变的核心机制,为后续复杂商城、笔记、打卡、管理类应用开发打下坚实基础。

2.2 应用场景

本待办应用具备极高的实用性,日常适配多场景使用:

  • 个人日常管理:记录购物清单、每日计划、学习任务、运动打卡事项

  • 学习实训场景:记录课程作业、学习目标、技术学习计划

  • 工作办公场景:管理日常工作任务、跟进项目进度、记录待办工作

  • 习惯养成场景:记录每日阅读、健身、作息计划,辅助自律习惯养成

2.3 完整功能特性

本项目摒弃极简Demo残缺功能,实现商用级完整功能闭环:

  1. 新增待办任务:输入任务内容,点击按钮添加任务,自动生成唯一ID与创建时间

  2. 任务状态标记:通过复选框切换任务完成/未完成状态,已完成任务自动添加删除线、置灰

  3. 单条任务删除:精准删除指定无用任务,数据实时同步刷新

  4. 多维度筛选:支持全部任务、进行中任务、已完成任务三种模式切换查看

  5. 实时数据统计:实时展示待完成数量、总任务数、完成进度

  6. 批量清空功能:一键清除所有已完成任务,简化列表管理

  7. 空状态适配:无任务、无对应筛选数据时,友好空文案提示

2.4 页面结构分区

页面采用模块化分层设计,层级清晰、解耦性高:

  • 头部统计区:展示项目标题、待完成数量、完成进度统计

  • 任务输入区:文本输入框+新增按钮,实现任务录入

  • 筛选切换区:标签切换三种任务筛选状态

  • 任务列表区:动态渲染任务列表,自适应滚动

  • 底部功能区:全局任务统计、一键清空已完成任务

2.5 开发技术栈

技术模块

详细说明

开发系统

HarmonyOS NEXT 纯血鸿蒙

API版本

API 23 最新稳定版

开发语言

ArkTS 强类型语法

UI框架

ArkUI 声明式UI

状态管理

@State 响应式状态管理

列表渲染

ForEach + List 高性能渲染

代码规范

模块化@Builder组件封装、单一职责原则

三、开发环境搭建与项目结构

3.1 新建项目流程

本次项目基于 DevEco Studio 最新版创建,适配 API23 完整规范,创建步骤如下:

  1. 打开 DevEco Studio,点击 Create HarmonyOS Project

  2. 选择 Empty Ability 空模板(纯净无冗余代码)

  3. 设置项目名称为 TodoApp

  4. 选择编译SDK版本为 API 23

  5. 完成创建,等待项目依赖同步完毕

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精准定位对应任务,保证状态修改精准

八、项目拓展与进阶优化方向

本项目可基于现有完整架构持续迭代,适配进阶开发学习与商用改造:

  1. 数据持久化:接入Preferences轻量存储,实现应用重启数据不丢失

  2. 任务优先级:新增高/中/低优先级标记,颜色区分展示

  3. 编辑功能:新增任务长按编辑、修改任务内容

  4. 动画优化:新增新增、删除、状态切换过渡动画

  5. 分类管理:新增任务分类标签,实现分类筛选

  6. 滑动删除:实现列表项右滑删除交互

九、全文总结

本文基于 HarmonyOS NEXT API23 最新规范,从零开发了一款功能完整、代码规范、UI精美、逻辑严谨的Todo待办事项应用。全程深度讲解Interface数据结构、@State状态管理、列表渲染、条件渲染、组件封装等核心知识点,同时解决新手高频报错与性能问题。

该项目覆盖鸿蒙ArkUI入门80%核心基础知识点,是零基础夯实鸿蒙开发能力、理解数据驱动UI思想、积累项目实战经验的标杆案例,完全满足课程实训、课程设计、毕设练手、CSDN优质原创发文需求。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐