📖 鸿蒙NEXT开发实战系列 | 第19篇 | 实战篇 🎯 适合人群:学完状态管理基础的开发者 ⏰ 阅读时间:约20分钟 | 💻 开发环境:DevEco Studio 5.0+


上一篇18-进阶篇-状态管理高级技巧 下一篇20-实战篇-网络请求与数据持久化


📑 目录


一、前言:为什么要实战?

学了那么多状态管理的理论知识:@State@Link@Provide@ConsumeAppStorage... 是不是感觉"我懂了,但又好像没完全懂"?

光看理论不练手等于白学!

今天我们就用鸿蒙的状态管理,从零实现一个完整的Todo应用。这个应用虽然简单,但麻雀虽小五脏俱增,涵盖了:

  • 增删改查:完整的CRUD操作

  • 状态管理:@State管理局部状态、@Provide/@Consume跨组件通信

  • 持久化:AppStorage实现数据持久化存储

  • 组件化:合理的组件拆分和状态设计

总共约200行代码,带你打通状态管理的任督二脉!


二、项目架构设计

2.1 功能需求分析

我们的Todo应用需要实现以下功能:

功能

说明

✅ 添加待办

输入框输入内容,点击添加

✅ 删除待办

左滑或点击删除按钮

✅ 完成标记

点击复选框标记完成状态

✅ 编辑待办

双击进入编辑模式

✅ 数据持久化

应用重启后数据不丢失

✅ 统计展示

显示总数、已完成数

2.2 状态管理方案选型

针对不同场景,我们选择不同的状态管理方案:

┌─────────────────────────────────────────────────────────┐
│                     状态管理方案选型                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   ┌─────────────┐      ┌─────────────┐                 │
│   │   @State    │──────│   @Link     │                 │
│   │  组件内部   │      │  父子通信   │                 │
│   └─────────────┘      └─────────────┘                 │
│                                                         │
│   ┌─────────────┐      ┌─────────────┐                 │
│   │  @Provide   │──────│  @Consume   │                 │
│   │  跨层提供   │      │  跨层消费   │                 │
│   └─────────────┘      └─────────────┘                 │
│                                                         │
│   ┌─────────────────────────────────────┐              │
│   │           AppStorage                │              │
│   │        全局状态 + 持久化            │              │
│   └─────────────────────────────────────┘              │
│                                                         │
└─────────────────────────────────────────────────────────┘

具体方案

  1. TodoList数据:使用@StorageLink链接AppStorage,实现全局共享+持久化

  2. 输入框内容:使用@State管理组件局部状态

  3. 子组件属性:使用@Prop接收父组件数据(单向数据流)

2.3 项目结构规划

TodoApp/
├── entry/src/main/ets/
│   ├── entryability/
│   │   └── EntryAbility.ets          # 应用入口
│   ├── pages/
│   │   └── Index.ets                 # 主页面
│   ├── model/
│   │   └── TodoItem.ets              # 数据模型
│   ├── components/
│   │   └── TodoListItem.ets          # 列表项组件
│   └── utils/
│       └── StorageUtils.ets          # 存储工具类

三、核心代码实现

3.1 数据模型定义

首先定义Todo的数据结构:

// model/TodoItem.ets

/**
 * Todo数据模型
 * @property id 唯一标识
 * @property content 待办内容
 * @property isCompleted 是否完成
 * @property createdAt 创建时间
 */
export class TodoItem {
  id: string = ''
  content: string = ''
  isCompleted: boolean = false
  createdAt: number = Date.now()

  constructor(content: string) {
    this.id = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    this.content = content
    this.isCompleted = false
    this.createdAt = Date.now()
  }
}

设计说明

  • 使用id作为唯一标识,采用时间戳+随机数的方式生成

  • createdAt记录创建时间,方便后续扩展排序功能

  • 构造函数只需传入content,其他字段自动生成

3.2 TodoStore状态管理类

创建一个状态管理类,集中管理Todo相关的状态和方法:

// store/TodoStore.ets

import { TodoItem } from '../model/TodoItem'
import { preferences } from '@kit.ArkData'

const STORAGE_KEY = 'todo_list_data'

/**
 * Todo状态管理类
 * 使用Preferences实现数据持久化
 */
export class TodoStore {
  private static instance: TodoStore | null = null
  private prefs: preferences.Preferences | null = null

  // 单例模式
  static getInstance(): TodoStore {
    if (!TodoStore.instance) {
      TodoStore.instance = new TodoStore()
    }
    return TodoStore.instance
  }

  /**
   * 初始化存储
   */
  async init(context: Context): Promise<void> {
    try {
      this.prefs = await preferences.getPreferences(context, 'todo_store')
    } catch (err) {
      console.error('TodoStore初始化失败:', err)
    }
  }

  /**
   * 加载数据
   */
  async loadData(): Promise<TodoItem[]> {
    try {
      if (!this.prefs) return []
      const data = await this.prefs.get(STORAGE_KEY, '[]') as string
      return JSON.parse(data) as TodoItem[]
    } catch (err) {
      console.error('加载数据失败:', err)
      return []
    }
  }

  /**
   * 保存数据
   */
  async saveData(list: TodoItem[]): Promise<void> {
    try {
      if (!this.prefs) return
      await this.prefs.put(STORAGE_KEY, JSON.stringify(list))
      await this.prefs.flush()
    } catch (err) {
      console.error('保存数据失败:', err)
    }
  }
}

设计说明

  • 采用单例模式,全局共享一个Store实例

  • 使用Preferences进行轻量级数据持久化

  • 提供异步的loadDatasaveData方法

3.3 主页面布局实现

主页面是整个应用的核心,负责布局和状态管理:

// pages/Index.ets

import { TodoItem } from '../model/TodoItem'
import { TodoStore } from '../store/TodoStore'
import { TodoListItem } from '../components/TodoListItem'

@Entry
@Component
struct Index {
  // ============ 状态定义 ============

  // 使用@State管理本地状态
  @State todoList: TodoItem[] = []
  @State inputText: string = ''
  @State isLoading: boolean = true

  // Store实例
  private store: TodoStore = TodoStore.getInstance()

  // ============ 生命周期 ============

  aboutToAppear(): void {
    this.initApp()
  }

  /**
   * 初始化应用
   */
  private async initApp(): Promise<void> {
    // 获取UIContext用于显示弹窗
    const uiContext = this.getUIContext().getHostContext()!

    // 初始化Store
    await this.store.init(uiContext)

    // 加载数据
    const data = await this.store.loadData()
    this.todoList = data
    this.isLoading = false
  }

  /**
   * 保存数据到持久化存储
   */
  private async saveData(): Promise<void> {
    await this.store.saveData(this.todoList)
  }

  // ============ 业务方法 ============

  /**
   * 添加待办
   */
  private addTodo(): void {
    const content = this.inputText.trim()
    if (!content) return

    // 创建新Todo并添加到列表头部
    const newTodo = new TodoItem(content)
    this.todoList = [newTodo, ...this.todoList]

    // 清空输入框
    this.inputText = ''

    // 持久化保存
    this.saveData()
  }

  /**
   * 切换完成状态
   */
  private toggleTodo(id: string): void {
    const index = this.todoList.findIndex(item => item.id === id)
    if (index !== -1) {
      // 注意:需要创建新数组触发状态更新
      const newList = [...this.todoList]
      newList[index] = {
        ...newList[index],
        isCompleted: !newList[index].isCompleted
      }
      this.todoList = newList
      this.saveData()
    }
  }

  /**
   * 删除待办
   */
  private deleteTodo(id: string): void {
    this.todoList = this.todoList.filter(item => item.id !== id)
    this.saveData()
  }

  // ============ 计算属性 ============

  /**
   * 已完成数量
   */
  private get completedCount(): number {
    return this.todoList.filter(item => item.isCompleted).length
  }

  /**
   * 总数量
   */
  private get totalCount(): number {
    return this.todoList.length
  }

  // ============ UI构建 ============

  build() {
    Column() {
      // ---- 标题区域 ----
      this.TitleSection()

      // ---- 输入区域 ----
      this.InputSection()

      // ---- 统计区域 ----
      this.StatsSection()

      // ---- 列表区域 ----
      this.ListSection()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  /**
   * 标题区域
   */
  @Builder
  TitleSection() {
    Row() {
      Text('我的待办')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 40, bottom: 20 })
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 输入区域
   */
  @Builder
  InputSection() {
    Row() {
      TextInput({ text: this.inputText, placeholder: '请输入待办内容...' })
        .layoutWeight(1)
        .height(48)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 16, right: 16 })
        .fontSize(16)
        .onChange((value: string) => {
          this.inputText = value
        })
        .onSubmit(() => {
          this.addTodo()
        })

      Button('添加')
        .width(80)
        .height(48)
        .margin({ left: 12 })
        .backgroundColor('#4CAF50')
        .borderRadius(8)
        .onClick(() => {
          this.addTodo()
        })
    }
    .width('100%')
    .padding({ left: 20, right: 20 })
  }

  /**
   * 统计区域
   */
  @Builder
  StatsSection() {
    Row() {
      Text(`共 ${this.totalCount} 项,已完成 ${this.completedCount} 项`)
        .fontSize(14)
        .fontColor('#666666')
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 16, bottom: 8 })
  }

  /**
   * 列表区域
   */
  @Builder
  ListSection() {
    if (this.isLoading) {
      // 加载状态
      Column() {
        LoadingProgress()
          .width(48)
          .height(48)
        Text('加载中...')
          .fontSize(14)
          .fontColor('#999999')
          .margin({ top: 12 })
      }
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
    } else if (this.todoList.length === 0) {
      // 空状态
      Column() {
        Text('📝')
          .fontSize(48)
        Text('暂无待办事项')
          .fontSize(16)
          .fontColor('#999999')
          .margin({ top: 12 })
        Text('点击上方输入框添加')
          .fontSize(14)
          .fontColor('#CCCCCC')
          .margin({ top: 8 })
      }
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
    } else {
      // 列表
      List({ space: 8 }) {
        ForEach(this.todoList, (item: TodoItem) => {
          ListItem() {
            TodoListItem({
              item: item,
              onToggle: (id: string) => this.toggleTodo(id),
              onDelete: (id: string) => this.deleteTodo(id)
            })
          }
        }, (item: TodoItem) => item.id)
      }
      .layoutWeight(1)
      .padding({ left: 20, right: 20, top: 8 })
      .divider({
        strokeWidth: 0.5,
        color: '#EEEEEE'
      })
    }
  }
}

关键代码说明

  1. 状态更新触发机制

    // 必须创建新数组才能触发UI更新
    const newList = [...this.todoList]
    newList[index] = { ...newList[index], isCompleted: !newList[index].isCompleted }
    this.todoList = newList
  2. ForEach的keyGenerator

    ForEach(this.todoList, (item: TodoItem) => {
      // 列表项
    }, (item: TodoItem) => item.id)  // 使用id作为唯一标识

3.4 TodoListItem组件实现

将单个Todo项抽取为独立组件:

// components/TodoListItem.ets

import { TodoItem } from '../model/TodoItem'

/**
 * Todo列表项组件
 */
@Component
struct TodoListItem {
  // 使用@Prop接收父组件数据
  @Prop item: TodoItem

  // 回调函数
  onToggle: (id: string) => void = () => {}
  onDelete: (id: string) => void = () => {}

  build() {
    Row() {
      // 复选框
      Checkbox()
        .select(this.item.isCompleted)
        .selectedColor('#4CAF50')
        .width(24)
        .height(24)
        .onChange((value: boolean) => {
          this.onToggle(this.item.id)
        })

      // 内容文本
      Text(this.item.content)
        .fontSize(16)
        .fontColor(this.item.isCompleted ? '#999999' : '#333333')
        .decoration({
          type: this.item.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None,
          color: '#999999'
        })
        .layoutWeight(1)
        .margin({ left: 12 })
        .maxLines(3)

      // 删除按钮
      Button('删除')
        .width(60)
        .height(32)
        .fontSize(14)
        .backgroundColor('#FF5252')
        .borderRadius(4)
        .onClick(() => {
          this.onDelete(this.item.id)
        })
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({
      radius: 4,
      color: '#1A000000',
      offsetX: 0,
      offsetY: 2
    })
  }
}

组件设计说明

  1. @Prop装饰器

    • 用于接收父组件传递的数据

    • 单向数据流:父组件更新会同步到子组件

    • 子组件不能直接修改@Prop装饰的属性

  2. 回调函数模式

    • 通过onToggleonDelete回调通知父组件

    • 实际的状态修改在父组件中完成


四、持久化存储实现

4.1 AppStorage配置

entryAbility中配置AppStorage:

// entryability/EntryAbility.ets

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { window } from '@kit.ArkUI'

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate')
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy')
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate')

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err))
        return
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.')
    })
  }

  onWindowStageDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy')
  }

  onForeground(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground')
  }

  onBackground(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground')
  }
}

4.2 数据序列化与反序列化

为了确保数据能够正确存储和读取,需要注意序列化问题:

// 模型类需要提供序列化方法
export class TodoItem {
  id: string = ''
  content: string = ''
  isCompleted: boolean = false
  createdAt: number = Date.now()

  constructor(content: string) {
    this.id = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    this.content = content
    this.isCompleted = false
    this.createdAt = Date.now()
  }

  /**
   * 转换为可序列化的对象
   */
  toJSON(): object {
    return {
      id: this.id,
      content: this.content,
      isCompleted: this.isCompleted,
      createdAt: this.createdAt
    }
  }

  /**
   * 从对象创建实例
   */
  static fromJSON(json: any): TodoItem {
    const item = new TodoItem('')
    item.id = json.id || ''
    item.content = json.content || ''
    item.isCompleted = json.isCompleted || false
    item.createdAt = json.createdAt || Date.now()
    return item
  }
}

五、完整源码汇总

为了方便大家直接复制运行,这里给出完整的源码汇总:

5.1 TodoItem.ets(数据模型)

/**
 * Todo数据模型
 */
export class TodoItem {
  id: string = ''
  content: string = ''
  isCompleted: boolean = false
  createdAt: number = Date.now()

  constructor(content: string) {
    this.id = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
    this.content = content
    this.isCompleted = false
    this.createdAt = Date.now()
  }
}

5.2 TodoStore.ets(状态管理)

import { TodoItem } from '../model/TodoItem'
import { preferences } from '@kit.ArkData'

const STORAGE_KEY = 'todo_list_data'

/**
 * Todo状态管理类
 */
export class TodoStore {
  private static instance: TodoStore | null = null
  private prefs: preferences.Preferences | null = null

  static getInstance(): TodoStore {
    if (!TodoStore.instance) {
      TodoStore.instance = new TodoStore()
    }
    return TodoStore.instance
  }

  async init(context: Context): Promise<void> {
    try {
      this.prefs = await preferences.getPreferences(context, 'todo_store')
    } catch (err) {
      console.error('TodoStore初始化失败:', err)
    }
  }

  async loadData(): Promise<TodoItem[]> {
    try {
      if (!this.prefs) return []
      const data = await this.prefs.get(STORAGE_KEY, '[]') as string
      return JSON.parse(data) as TodoItem[]
    } catch (err) {
      console.error('加载数据失败:', err)
      return []
    }
  }

  async saveData(list: TodoItem[]): Promise<void> {
    try {
      if (!this.prefs) return
      await this.prefs.put(STORAGE_KEY, JSON.stringify(list))
      await this.prefs.flush()
    } catch (err) {
      console.error('保存数据失败:', err)
    }
  }
}

5.3 TodoListItem.ets(列表项组件)

import { TodoItem } from '../model/TodoItem'

/**
 * Todo列表项组件
 */
@Component
struct TodoListItem {
  @Prop item: TodoItem
  onToggle: (id: string) => void = () => {}
  onDelete: (id: string) => void = () => {}

  build() {
    Row() {
      Checkbox()
        .select(this.item.isCompleted)
        .selectedColor('#4CAF50')
        .width(24)
        .height(24)
        .onChange((value: boolean) => {
          this.onToggle(this.item.id)
        })

      Text(this.item.content)
        .fontSize(16)
        .fontColor(this.item.isCompleted ? '#999999' : '#333333')
        .decoration({
          type: this.item.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None,
          color: '#999999'
        })
        .layoutWeight(1)
        .margin({ left: 12 })
        .maxLines(3)

      Button('删除')
        .width(60)
        .height(32)
        .fontSize(14)
        .backgroundColor('#FF5252')
        .borderRadius(4)
        .onClick(() => {
          this.onDelete(this.item.id)
        })
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({
      radius: 4,
      color: '#1A000000',
      offsetX: 0,
      offsetY: 2
    })
  }
}

5.4 Index.ets(主页面)

import { TodoItem } from '../model/TodoItem'
import { TodoStore } from '../store/TodoStore'
import { TodoListItem } from '../components/TodoListItem'

@Entry
@Component
struct Index {
  @State todoList: TodoItem[] = []
  @State inputText: string = ''
  @State isLoading: boolean = true

  private store: TodoStore = TodoStore.getInstance()

  aboutToAppear(): void {
    this.initApp()
  }

  private async initApp(): Promise<void> {
    const uiContext = this.getUIContext().getHostContext()!
    await this.store.init(uiContext)
    const data = await this.store.loadData()
    this.todoList = data
    this.isLoading = false
  }

  private async saveData(): Promise<void> {
    await this.store.saveData(this.todoList)
  }

  private addTodo(): void {
    const content = this.inputText.trim()
    if (!content) return

    const newTodo = new TodoItem(content)
    this.todoList = [newTodo, ...this.todoList]
    this.inputText = ''
    this.saveData()
  }

  private toggleTodo(id: string): void {
    const index = this.todoList.findIndex(item => item.id === id)
    if (index !== -1) {
      const newList = [...this.todoList]
      newList[index] = {
        ...newList[index],
        isCompleted: !newList[index].isCompleted
      }
      this.todoList = newList
      this.saveData()
    }
  }

  private deleteTodo(id: string): void {
    this.todoList = this.todoList.filter(item => item.id !== id)
    this.saveData()
  }

  private get completedCount(): number {
    return this.todoList.filter(item => item.isCompleted).length
  }

  private get totalCount(): number {
    return this.todoList.length
  }

  build() {
    Column() {
      // 标题
      Row() {
        Text('我的待办')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 40, bottom: 20 })
      .justifyContent(FlexAlign.Center)

      // 输入区域
      Row() {
        TextInput({ text: this.inputText, placeholder: '请输入待办内容...' })
          .layoutWeight(1)
          .height(48)
          .backgroundColor('#FFFFFF')
          .borderRadius(8)
          .padding({ left: 16, right: 16 })
          .fontSize(16)
          .onChange((value: string) => {
            this.inputText = value
          })
          .onSubmit(() => {
            this.addTodo()
          })

        Button('添加')
          .width(80)
          .height(48)
          .margin({ left: 12 })
          .backgroundColor('#4CAF50')
          .borderRadius(8)
          .onClick(() => {
            this.addTodo()
          })
      }
      .width('100%')
      .padding({ left: 20, right: 20 })

      // 统计
      Row() {
        Text(`共 ${this.totalCount} 项,已完成 ${this.completedCount} 项`)
          .fontSize(14)
          .fontColor('#666666')
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 16, bottom: 8 })

      // 列表
      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .width(48)
            .height(48)
          Text('加载中...')
            .fontSize(14)
            .fontColor('#999999')
            .margin({ top: 12 })
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      } else if (this.todoList.length === 0) {
        Column() {
          Text('📝')
            .fontSize(48)
          Text('暂无待办事项')
            .fontSize(16)
            .fontColor('#999999')
            .margin({ top: 12 })
          Text('点击上方输入框添加')
            .fontSize(14)
            .fontColor('#CCCCCC')
            .margin({ top: 8 })
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      } else {
        List({ space: 8 }) {
          ForEach(this.todoList, (item: TodoItem) => {
            ListItem() {
              TodoListItem({
                item: item,
                onToggle: (id: string) => this.toggleTodo(id),
                onDelete: (id: string) => this.deleteTodo(id)
              })
            }
          }, (item: TodoItem) => item.id)
        }
        .layoutWeight(1)
        .padding({ left: 20, right: 20, top: 8 })
        .divider({
          strokeWidth: 0.5,
          color: '#EEEEEE'
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

六、运行效果展示

6.1 主界面效果

┌─────────────────────────────────┐
│           我的待办              │
├─────────────────────────────────┤
│  ┌────────────────────┐ ┌────┐ │
│  │ 请输入待办内容...   │ │添加│ │
│  └────────────────────┘ └────┘ │
├─────────────────────────────────┤
│  共 3 项,已完成 1 项          │
├─────────────────────────────────┤
│  ┌─────────────────────────────┐│
│  │ ☑ 学习鸿蒙状态管理         ││
│  │   ─────────────────         ││
│  │   删除                      ││
│  └─────────────────────────────┘│
│  ┌─────────────────────────────┐│
│  │ ☐ 完成Todo应用实战         ││
│  │   删除                      ││
│  └─────────────────────────────┘│
│  ┌─────────────────────────────┐│
│  │ ☐ 写技术博客               ││
│  │   删除                      ││
│  └─────────────────────────────┘│
└─────────────────────────────────┘

6.2 功能演示

操作

效果

输入内容点击添加

新待办添加到列表顶部

点击复选框

标记完成/取消完成,文本添加删除线

点击删除按钮

删除该待办项

关闭应用重新打开

数据自动加载,状态保持


七、总结与扩展

7.1 知识点回顾

通过这个Todo应用,我们实践了:

  1. @State:管理组件内部状态

  2. @Prop:父子组件单向数据传递

  3. @Builder:自定义构建函数,提高代码复用

  4. Preferences:轻量级数据持久化方案

  5. ForEach:列表渲染及key的使用

  6. 组件化开发:合理的组件拆分

7.2 扩展优化方向

如果你想进一步完善这个应用,可以考虑:

  1. 添加编辑功能:双击进入编辑模式

  2. 分类管理:支持创建不同的待办分类

  3. 优先级设置:支持设置高/中/低优先级

  4. 提醒功能:设置提醒时间,到时通知

  5. 云同步:接入云服务,实现多端同步

  6. 性能优化:大量数据时使用LazyForEach懒加载

7.3 状态管理最佳实践

通过这个项目,总结几点状态管理的最佳实践:

┌─────────────────────────────────────────────────────────┐
│              状态管理最佳实践                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 状态下沉:状态尽量放在使用它的最近组件               │
│  2. 单向数据流:父子组件通过props和回调通信              │
│  3. 不可变更新:修改数组/对象时创建新引用                 │
│  4. 持久化分离:数据存储逻辑与UI逻辑分离                 │
│  5. 类型安全:使用TypeScript定义明确的数据类型           │
│                                                         │
└─────────────────────────────────────────────────────────┘

系列文章推荐


标签

Todo应用 鸿蒙实战 状态管理 ArkUI 完整项目 @State @Prop Preferences 持久化存储 组件化开发


💡 下期预告:下一篇我们将学习网络请求与数据持久化的进阶用法,敬请期待!

📧 反馈交流:如有问题或建议,欢迎在评论区留言交流。

Logo

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

更多推荐