状态管理一文通:@State、@Prop、@Link、@Provide/Consume全解析

系列文章 :鸿蒙NEXT开发实战系列 -- 第3篇(共5篇) 适合人群 :有ArkUI基础,想深入理解状态管理的开发者 开发环境 :DevEco Studio 5.0.5+ | HarmonyOS NEXT (API 14) 阅读时长:约15分钟

上一篇:ArkUI组件库完全指南 | 下一篇:数据持久化与网络请求全攻略


一、引言:为什么状态管理如此重要?

在鸿蒙NEXT应用开发中,状态管理是构建动态UI的核心基石。想象一下,如果一个电商App的商品数量无法实时更新、用户的登录状态无法同步到各个页面、表单输入无法即时反馈------这样的应用体验将是灾难性的。

ArkUI采用声明式UI范式,其核心思想是"状态驱动UI":当状态(State)发生变化时,框架会自动更新对应的UI组件。这种模式相比传统的命令式UI,代码更简洁、逻辑更清晰、维护更方便。

本文将系统讲解ArkUI四大核心状态管理装饰器:@State@Prop@Link@Provide/@Consume,并通过完整的实战代码帮助你彻底掌握它们的使用场景和最佳实践。


二、@State装饰器:组件内的状态管家

2.1 基本概念

@State是最基础的状态装饰器,用于管理组件内部 的状态数据。被@State装饰的变量具有以下特性:

  • 响应式:变量值改变时,自动触发UI刷新

  • 私有化:仅在当前组件内可见和可修改

  • 初始化:支持在组件创建时进行初始化

2.2 基础用法

复制代码
@Component
struct CounterComponent {
  // 使用@State装饰器声明状态变量
  @State count: number = 0
  
  build() {
    Column() {
      Text(`当前计数:${this.count}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
      
      Row() {
        Button('减少')
          .onClick(() => {
            // 直接修改@State变量,UI会自动更新
            this.count--
          })
          .margin({ right: 10 })
        
        Button('增加')
          .onClick(() => {
            this.count++
          })
          .margin({ left: 10 })
      }
    }
    .width('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

2.3 支持的数据类型

@State支持多种数据类型,但不同类型的行为略有差异:

复制代码
@Component
struct StateTypesDemo {
  // 基础类型:number、string、boolean
  @State count: number = 0
  @State message: string = 'Hello'
  @State isActive: boolean = true
  
  // 对象类型:需要整体赋值才能触发UI更新
  @State user: UserInfo = { name: '张三', age: 25 }
  
  // 数组类型:需要整体赋值或使用splice等方法
  @State items: string[] = ['苹果', '香蕉', '橙子']
  
  build() {
    Column() {
      // 基础类型示例
      Text(`计数:${this.count}`)
      Button('更新计数')
        .onClick(() => {
          this.count++  // 直接赋值即可
        })
      
      // 对象类型示例
      Text(`用户名:${this.user.name}`)
      Button('更新用户名')
        .onClick(() => {
          // 错误方式:this.user.name = '李四'  // 不会触发UI更新
          // 正确方式:整体赋值
          this.user = { ...this.user, name: '李四' }
        })
      
      // 数组类型示例
      ForEach(this.items, (item: string) => {
        Text(item)
      })
      Button('添加水果')
        .onClick(() => {
          // 错误方式:this.items.push('葡萄')  // 不会触发UI更新
          // 正确方式:整体赋值
          this.items = [...this.items, '葡萄']
        })
    }
  }
}

interface UserInfo {
  name: string
  age: number
}

2.4 踩坑记录:对象属性修改不触发更新

问题描述:直接修改对象的属性值,UI没有更新。

错误代码

复制代码
@State user: UserInfo = { name: '张三', age: 25 }

// 点击后UI没有变化
Button('修改名字')
  .onClick(() => {
    this.user.name = '李四'  // 错误:直接修改属性
  })

正确代码

复制代码
// 正确方式:整体赋值新对象
Button('修改名字')
  .onClick(() => {
    this.user = { ...this.user, name: '李四' }  // 使用展开运算符创建新对象
  })

原理说明@State通过引用比较来检测变化,直接修改属性不会改变对象引用,因此框架认为状态没有变化。


三、@Prop装饰器:父子组件的单向数据流

3.1 基本概念

@Prop用于实现父组件到子组件的单向数据传递。特点如下:

  • 单向绑定:数据只能从父组件流向子组件

  • 本地副本:子组件接收到的是父组件数据的副本,修改不会影响父组件

  • 自动同步:父组件数据变化时,子组件自动更新

3.2 基础用法

复制代码
// 子组件:接收父组件传递的数据
@Component
struct ChildComponent {
  @Prop title: string  // 使用@Prop接收父组件数据
  @Prop count: number
  
  build() {
    Column() {
      Text(this.title)
        .fontSize(20)
        .margin({ bottom: 10 })
      
      Text(`子组件计数:${this.count}`)
        .fontSize(16)
    }
    .padding(15)
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }
}

// 父组件:传递数据给子组件
@Entry
@Component
struct ParentComponent {
  @State parentTitle: string = '来自父组件的标题'
  @State parentCount: number = 0
  
  build() {
    Column() {
      // 父组件的UI
      Text(`父组件计数:${this.parentCount}`)
        .fontSize(20)
        .margin({ bottom: 20 })
      
      Button('父组件更新数据')
        .onClick(() => {
          this.parentCount++
          this.parentTitle = `更新后的标题 ${this.parentCount}`
        })
        .margin({ bottom: 20 })
      
      // 传递数据给子组件
      ChildComponent({
        title: this.parentTitle,  // 将父组件的title传递给子组件
        count: this.parentCount   // 将父组件的count传递给子组件
      })
    }
    .width('100%')
    .padding(20)
  }
}

3.3 @Prop的单向性演示

复制代码
@Component
struct PropDemoChild {
  @Prop message: string
  @State localCount: number = 0
  
  build() {
    Column() {
      Text(`接收到的消息:${this.message}`)
        .margin({ bottom: 10 })
      
      Text(`本地计数:${this.localCount}`)
        .margin({ bottom: 10 })
      
      Button('子组件修改本地状态')
        .onClick(() => {
          this.localCount++  // 修改本地状态,子组件UI更新
        })
        .margin({ bottom: 10 })
      
      Button('尝试修改Prop(无效)')
        .onClick(() => {
          // this.message = '新消息'  // 编译错误:@Prop是只读的
          console.info('Prop不能在子组件中修改')
        })
    }
    .padding(15)
    .backgroundColor('#E3F2FD')
    .borderRadius(8)
  }
}

@Entry
@Component
struct PropDemoParent {
  @State parentMessage: string = 'Hello from Parent'
  
  build() {
    Column() {
      Text('父组件')
        .fontSize(24)
        .margin({ bottom: 20 })
      
      Button('更新父组件消息')
        .onClick(() => {
          this.parentMessage = `消息更新于 ${new Date().toLocaleTimeString()}`
        })
        .margin({ bottom: 20 })
      
      PropDemoChild({
        message: this.parentMessage
      })
    }
    .width('100%')
    .padding(20)
  }
}

四、@Link装饰器:父子组件的双向数据绑定

4.1 基本概念

@Link用于实现父子组件之间的双向数据绑定。特点如下:

  • 双向绑定:父子组件中任何一方修改数据,另一方都会同步更新

  • 同一引用:父子组件共享同一个数据源

  • 必须初始化 :父组件必须使用$$操作符传递数据

4.2 基础用法

复制代码
// 子组件:使用@Link实现双向绑定
@Component
struct LinkChildComponent {
  @Link count: number  // 使用@Link装饰器
  @Link message: string
  
  build() {
    Column() {
      Text(`子组件显示:${this.message}`)
        .fontSize(18)
        .margin({ bottom: 10 })
      
      Text(`计数:${this.count}`)
        .fontSize(24)
        .margin({ bottom: 15 })
      
      Row() {
        Button('子组件 -')
          .onClick(() => {
            this.count--  // 子组件修改,父组件同步更新
          })
          .margin({ right: 10 })
        
        Button('子组件 +')
          .onClick(() => {
            this.count++
          })
      }
      
      Button('修改消息')
        .onClick(() => {
          this.message = '子组件修改的消息'
        })
        .margin({ top: 10 })
    }
    .padding(15)
    .backgroundColor('#E8F5E9')
    .borderRadius(8)
  }
}

// 父组件
@Entry
@Component
struct LinkParentComponent {
  @State parentCount: number = 0
  @State parentMessage: string = '来自父组件的消息'
  
  build() {
    Column() {
      Text('父组件')
        .fontSize(24)
        .margin({ bottom: 10 })
      
      Text(`父组件显示:${this.parentMessage}`)
        .fontSize(18)
        .margin({ bottom: 10 })
      
      Text(`父组件计数:${this.parentCount}`)
        .fontSize(20)
        .margin({ bottom: 15 })
      
      Row() {
        Button('父组件 -')
          .onClick(() => {
            this.parentCount--  // 父组件修改,子组件同步更新
          })
          .margin({ right: 10 })
        
        Button('父组件 +')
          .onClick(() => {
            this.parentCount++
          })
      }
      
      // 使用$$操作符传递数据引用
      LinkChildComponent({
        count: $parentCount,      // $$操作符传递引用
        message: $parentMessage
      })
    }
    .width('100%')
    .padding(20)
  }
}

4.3 @Link与@Prop的对比

复制代码
// 对比示例:展示@Link和@Prop的区别

// 使用@Prop的子组件
@Component
struct PropChild {
  @Prop value: number
  
  build() {
    Column() {
      Text(`Prop子组件:${this.value}`)
      Button('Prop修改')
        .onClick(() => {
          // this.value = 100  // 编译错误,@Prop是只读的
        })
    }
    .padding(10)
    .backgroundColor('#FFEBEE')
  }
}

// 使用@Link的子组件
@Component
struct LinkChild {
  @Link value: number
  
  build() {
    Column() {
      Text(`Link子组件:${this.value}`)
      Button('Link修改')
        .onClick(() => {
          this.value = 100  // 可以修改,父组件同步更新
        })
    }
    .padding(10)
    .backgroundColor('#E8F5E9')
  }
}

@Entry
@Component
struct ComparisonDemo {
  @State propValue: number = 0
  @State linkValue: number = 0
  
  build() {
    Column() {
      Text(`Prop父组件:${this.propValue}`)
      Text(`Link父组件:${this.linkValue}`)
      
      PropChild({ value: this.propValue })
      LinkChild({ value: $linkValue })
    }
    .width('100%')
    .padding(20)
  }
}

4.4 踩坑记录:@Link必须使用$$操作符

问题描述:子组件状态没有同步更新。

错误代码

复制代码
// 错误:没有使用$$操作符
LinkChildComponent({
  count: this.parentCount,  // 错误:这会创建副本,不是引用
})

正确代码

复制代码
// 正确:使用$$操作符传递引用
LinkChildComponent({
  count: $parentCount,  // 正确:传递数据引用
})

五、@Provide/@Consume装饰器:跨组件层级的数据共享

5.1 基本概念

@Provide@Consume用于实现跨组件层级的数据共享,无需通过中间组件逐层传递。特点如下:

  • 跨层级传递:可以在任意深度的子组件中访问数据

  • 双向绑定@Provide@Consume之间是双向的

  • 一对多 :一个@Provide可以对应多个@Consume

5.2 基础用法

复制代码
// 顶层组件:使用@Provide提供数据
@Entry
@Component
struct AppRoot {
  @Provide('themeColor') themeColor: string = '#1890FF'  // 提供主题颜色
  @Provide('language') language: string = 'zh-CN'  // 提供语言设置
  
  build() {
    Column() {
      Text('应用根组件')
        .fontSize(24)
        .margin({ bottom: 20 })
      
      Row() {
        Button('切换主题')
          .onClick(() => {
            this.themeColor = this.themeColor === '#1890FF' ? '#52C41A' : '#1890FF'
          })
          .margin({ right: 10 })
        
        Button('切换语言')
          .onClick(() => {
            this.language = this.language === 'zh-CN' ? 'en-US' : 'zh-CN'
          })
      }
      .margin({ bottom: 30 })
      
      // 中间组件
      MiddleComponent()
    }
    .width('100%')
    .padding(20)
  }
}

// 中间组件:不处理数据,只负责传递
@Component
struct MiddleComponent {
  build() {
    Column() {
      Text('中间组件(不消费数据)')
        .margin({ bottom: 20 })
      
      // 深层子组件
      DeepChildComponent()
    }
    .padding(15)
    .backgroundColor('#F0F0F0')
    .borderRadius(8)
  }
}

// 深层子组件:使用@Consume消费数据
@Component
struct DeepChildComponent {
  @Consume('themeColor') themeColor: string  // 消费主题颜色
  @Consume('language') language: string  // 消费语言设置
  
  build() {
    Column() {
      Text('深层子组件')
        .fontSize(18)
        .fontColor(this.themeColor)  // 使用主题颜色
        .margin({ bottom: 10 })
      
      Text(`当前语言:${this.language}`)
        .margin({ bottom: 10 })
      
      Text('这是一段示例文本')
        .fontColor(this.themeColor)
    }
    .padding(15)
    .border({
      width: 2,
      color: this.themeColor,
      style: BorderStyle.Solid
    })
    .borderRadius(8)
  }
}

5.3 多层级消费示例

复制代码
// 全局状态管理示例

// 用户信息接口
interface UserInfo {
  name: string
  isLoggedIn: boolean
}

@Entry
@Component
struct GlobalStateDemo {
  @Provide('userInfo') userInfo: UserInfo = {
    name: '游客',
    isLoggedIn: false
  }
  @Provide('notification') notification: string = ''
  
  build() {
    Column() {
      Text('全局状态演示')
        .fontSize(24)
        .margin({ bottom: 20 })
      
      // 用户状态显示
      HeaderComponent()
      
      // 主要内容区
      ContentComponent()
      
      // 底部导航
      FooterComponent()
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 头部组件
@Component
struct HeaderComponent {
  @Consume('userInfo') userInfo: UserInfo
  @Consume('notification') notification: string
  
  build() {
    Row() {
      Text(this.userInfo.isLoggedIn ? `欢迎,${this.userInfo.name}` : '请登录')
        .fontSize(18)
      
      if (this.notification) {
        Text(this.notification)
          .fontSize(14)
          .fontColor('#FF4D4F')
          .margin({ left: 20 })
      }
    }
    .width('100%')
    .padding(10)
    .backgroundColor('#E6F7FF')
  }
}

// 内容组件
@Component
struct ContentComponent {
  build() {
    Column() {
      Text('主要内容区域')
        .margin({ bottom: 20 })
      
      // 登录表单
      LoginFormComponent()
    }
    .flexGrow(1)
    .padding(20)
  }
}

// 登录表单组件
@Component
struct LoginFormComponent {
  @Consume('userInfo') userInfo: UserInfo
  @Consume('notification') notification: string
  @State username: string = ''
  
  build() {
    Column() {
      TextInput({ placeholder: '请输入用户名' })
        .onChange((value: string) => {
          this.username = value
        })
        .margin({ bottom: 10 })
      
      Button('登录')
        .onClick(() => {
          if (this.username) {
            // 修改全局状态
            this.userInfo = {
              name: this.username,
              isLoggedIn: true
            }
            this.notification = '登录成功!'
          }
        })
    }
    .padding(15)
    .backgroundColor('#FFF7E6')
    .borderRadius(8)
  }
}

// 底部组件
@Component
struct FooterComponent {
  @Consume('userInfo') userInfo: UserInfo
  
  build() {
    Row() {
      Text(this.userInfo.isLoggedIn ? '已登录' : '未登录')
        .fontSize(14)
        .fontColor('#666666')
    }
    .width('100%')
    .padding(10)
    .justifyContent(FlexAlign.Center)
  }
}

5.4 @Provide/@Consume的双向绑定

复制代码
@Component
struct BidirectionalDemo {
  @Provide('sharedValue') sharedValue: number = 0
  
  build() {
    Column() {
      Text(`顶层组件值:${this.sharedValue}`)
        .fontSize(20)
        .margin({ bottom: 20 })
      
      Button('顶层组件 +')
        .onClick(() => {
          this.sharedValue++
        })
        .margin({ bottom: 20 })
      
      MiddleLayer()
    }
    .width('100%')
    .padding(20)
  }
}

@Component
struct MiddleLayer {
  build() {
    Column() {
      Text('中间层(不消费数据)')
        .margin({ bottom: 20 })
      
      BottomLayer()
    }
    .padding(15)
    .backgroundColor('#F5F5F5')
  }
}

@Component
struct BottomLayer {
  @Consume('sharedValue') sharedValue: number
  
  build() {
    Column() {
      Text(`底层组件值:${this.sharedValue}`)
        .fontSize(18)
        .margin({ bottom: 10 })
      
      Button('底层组件 +')
        .onClick(() => {
          this.sharedValue++  // 修改会同步到顶层组件
        })
    }
    .padding(15)
    .backgroundColor('#E8F5E9')
    .borderRadius(8)
  }
}

@Entry
@Component
struct BidirectionalParent {
  build() {
    BidirectionalDemo()
  }
}

六、状态管理最佳实践与常见陷阱

6.1 最佳实践

1. 合理选择装饰器
复制代码
// 场景1:组件内部状态 -> 使用@State
@Component
struct LocalStateExample {
  @State isVisible: boolean = false  // 仅组件内部使用
  
  build() {
    Column() {
      Button('切换显示')
        .onClick(() => {
          this.isVisible = !this.isVisible
        })
      
      if (this.isVisible) {
        Text('现在可见')
      }
    }
  }
}

// 场景2:父子组件单向传递 -> 使用@Prop
@Component
struct ParentComponent {
  @State title: string = '标题'
  
  build() {
    ChildComponent({ title: this.title })
  }
}

@Component
struct ChildComponent {
  @Prop title: string  // 只读,子组件不需要修改
  
  build() {
    Text(this.title)
  }
}

// 场景3:父子组件双向绑定 -> 使用@Link
@Component
struct FormParent {
  @State inputValue: string = ''
  
  build() {
    Column() {
      Text(`输入值:${this.inputValue}`)
      FormChild({ inputValue: $inputValue })
    }
  }
}

@Component
struct FormChild {
  @Link inputValue: string
  
  build() {
    TextInput({ text: this.inputValue })
      .onChange((value: string) => {
        this.inputValue = value  // 双向同步
      })
  }
}

// 场景4:跨层级共享 -> 使用@Provide/@Consume
@Entry
@Component
struct AppRoot {
  @Provide('globalTheme') theme: string = 'light'
  
  build() {
    DeepNestedComponent()  // 任意深度的子组件都可以访问
  }
}

@Component
struct DeepNestedComponent {
  @Consume('globalTheme') theme: string
  
  build() {
    Text(`当前主题:${this.theme}`)
  }
}
2. 避免过度使用@Provide/@Consume
复制代码
// 不推荐:过度使用@Provide/@Consume
@Entry
@Component
struct BadExample {
  @Provide('count') count: number = 0
  @Provide('name') name: string = ''
  @Provide('age') age: number = 0
  // ... 更多Provide
  
  build() {
    // 复杂的组件树
  }
}

// 推荐:使用状态管理类集中管理
class AppState {
  static count: number = 0
  static name: string = ''
  static age: number = 0
}

// 或者使用AppStorage
@Entry
@Component
struct GoodExample {
  @StorageLink('count') count: number = 0
  @StorageLink('name') name: string = ''
  
  build() {
    // 使用AppStorage管理全局状态
  }
}
3. 性能优化:避免不必要的状态更新
复制代码
@Component
struct OptimizedComponent {
  @State data: DataItem[] = []
  @State selectedItem: number = -1
  
  build() {
    List() {
      ForEach(this.data, (item: DataItem, index: number) => {
        ListItem() {
          Text(item.name)
            .backgroundColor(this.selectedItem === index ? '#E6F7FF' : '#FFFFFF')
            .onClick(() => {
              // 只更新选中状态,不重新渲染整个列表
              this.selectedItem = index
            })
        }
      }, (item: DataItem) => item.id.toString())  // 使用唯一key优化
    }
  }
}

interface DataItem {
  id: number
  name: string
}

6.2 常见陷阱与解决方案

陷阱1:@State对象属性修改不触发更新

问题代码

复制代码
@State user: UserInfo = { name: '张三', age: 25 }

Button('修改')
  .onClick(() => {
    this.user.name = '李四'  // 错误:不会触发UI更新
  })

解决方案

复制代码
// 方案1:整体赋值
this.user = { ...this.user, name: '李四' }

// 方案2:使用@Observed和@ObjectLink(详见下期文章)
@Observed
class User {
  name: string = '张三'
  age: number = 25
}
陷阱2:@Link忘记使用$$操作符

问题代码

复制代码
// 父组件
@State count: number = 0
ChildComponent({ count: this.count })  // 错误:传递的是值,不是引用

// 子组件
@Link count: number  // 子组件修改不会影响父组件

解决方案

复制代码
// 父组件
@State count: number = 0
ChildComponent({ count: $count })  // 正确:使用$$操作符传递引用
陷阱3:数组更新不触发UI刷新

问题代码

复制代码
@State list: string[] = ['a', 'b', 'c']

Button('添加')
  .onClick(() => {
    this.list.push('d')  // 错误:不会触发UI更新
  })

解决方案

复制代码
// 方案1:整体赋值
this.list = [...this.list, 'd']

// 方案2:使用splice
this.list.splice(this.list.length, 0, 'd')
this.list = [...this.list]  // 触发更新

// 方案3:使用ObservedV2(API 12+)
@ObservedV2
class ListManager {
  @Trace list: string[] = ['a', 'b', 'c']
}
陷阱4:@Provide/@Consume key不匹配

问题代码

复制代码
// 父组件
@Provide('theme') themeColor: string = 'blue'

// 子组件
@Consume('Theme') themeColor: string  // 错误:key不匹配(大小写不同)

解决方案

复制代码
// 确保key完全一致
@Provide('theme') themeColor: string = 'blue'
@Consume('theme') themeColor: string  // 正确

七、完整实战案例:TodoList应用

复制代码
// TodoList完整示例

// Todo项接口
interface TodoItem {
  id: number
  text: string
  completed: boolean
}

// 全局状态提供者
@Entry
@Component
struct TodoApp {
  @Provide('todos') todos: TodoItem[] = []
  @Provide('filter') filter: string = 'all'  // all, active, completed
  @State nextId: number = 1
  
  // 计算过滤后的列表
  getFilteredTodos(): TodoItem[] {
    switch (this.filter) {
      case 'active':
        return this.todos.filter(todo => !todo.completed)
      case 'completed':
        return this.todos.filter(todo => todo.completed)
      default:
        return this.todos
    }
  }
  
  build() {
    Column() {
      // 标题
      Text('TodoList应用')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
      
      // 添加Todo输入框
      AddTodoComponent({
        onAdd: (text: string) => {
          this.todos = [...this.todos, {
            id: this.nextId++,
            text,
            completed: false
          }]
        }
      })
      
      // 过滤器
      FilterComponent()
      
      // Todo列表
      TodoListComponent({
        todos: this.getFilteredTodos()
      })
      
      // 统计信息
      StatsComponent()
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 添加Todo组件
@Component
struct AddTodoComponent {
  @State inputText: string = ''
  onAdd: (text: string) => void = () => {}
  
  build() {
    Row() {
      TextInput({ placeholder: '添加新的待办事项' })
        .layoutWeight(1)
        .onChange((value: string) => {
          this.inputText = value
        })
        .margin({ right: 10 })
      
      Button('添加')
        .onClick(() => {
          if (this.inputText.trim()) {
            this.onAdd(this.inputText.trim())
            this.inputText = ''
          }
        })
    }
    .margin({ bottom: 20 })
  }
}

// 过滤器组件
@Component
struct FilterComponent {
  @Consume('filter') filter: string
  
  build() {
    Row() {
      Button('全部')
        .backgroundColor(this.filter === 'all' ? '#1890FF' : '#F0F0F0')
        .fontColor(this.filter === 'all' ? '#FFFFFF' : '#000000')
        .onClick(() => {
          this.filter = 'all'
        })
        .margin({ right: 10 })
      
      Button('未完成')
        .backgroundColor(this.filter === 'active' ? '#1890FF' : '#F0F0F0')
        .fontColor(this.filter === 'active' ? '#FFFFFF' : '#000000')
        .onClick(() => {
          this.filter = 'active'
        })
        .margin({ right: 10 })
      
      Button('已完成')
        .backgroundColor(this.filter === 'completed' ? '#1890FF' : '#F0F0F0')
        .fontColor(this.filter === 'completed' ? '#FFFFFF' : '#000000')
        .onClick(() => {
          this.filter = 'completed'
        })
    }
    .margin({ bottom: 20 })
  }
}

// Todo列表组件
@Component
struct TodoListComponent {
  @Prop todos: TodoItem[]
  @Consume('todos') allTodos: TodoItem[]
  
  build() {
    List() {
      ForEach(this.todos, (todo: TodoItem) => {
        ListItem() {
          TodoItemComponent({
            todo,
            onToggle: () => {
              // 更新todo状态
              const index = this.allTodos.findIndex(t => t.id === todo.id)
              if (index !== -1) {
                const newTodos = [...this.allTodos]
                newTodos[index] = { ...newTodos[index], completed: !newTodos[index].completed }
                this.allTodos = newTodos
              }
            },
            onDelete: () => {
              this.allTodos = this.allTodos.filter(t => t.id !== todo.id)
            }
          })
        }
        .margin({ bottom: 10 })
      }, (todo: TodoItem) => todo.id.toString())
    }
    .layoutWeight(1)
  }
}

// 单个Todo项组件
@Component
struct TodoItemComponent {
  @Prop todo: TodoItem
  onToggle: () => void = () => {}
  onDelete: () => void = () => {}
  
  build() {
    Row() {
      // 复选框
      Checkbox()
        .select(this.todo.completed)
        .onChange((value: boolean) => {
          this.onToggle()
        })
        .margin({ right: 10 })
      
      // 文本
      Text(this.todo.text)
        .fontSize(16)
        .decoration({ type: this.todo.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
        .layoutWeight(1)
      
      // 删除按钮
      Button('删除')
        .fontSize(12)
        .height(30)
        .backgroundColor('#FF4D4F')
        .onClick(() => {
          this.onDelete()
        })
    }
    .padding(15)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({ radius: 2, color: '#0000001A', offsetX: 0, offsetY: 2 })
  }
}

// 统计组件
@Component
struct StatsComponent {
  @Consume('todos') todos: TodoItem[]
  
  get activeCount(): number {
    return this.todos.filter(todo => !todo.completed).length
  }
  
  get completedCount(): number {
    return this.todos.filter(todo => todo.completed).length
  }
  
  build() {
    Row() {
      Text(`总计:${this.todos.length}`)
        .margin({ right: 20 })
      
      Text(`未完成:${this.activeCount}`)
        .margin({ right: 20 })
      
      Text(`已完成:${this.completedCount}`)
    }
    .width('100%')
    .padding(15)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#FAFAFA')
    .borderRadius(8)
  }
}

八、总结与下期预告

本文要点回顾

  1. @State:组件内部状态管理,支持基础类型和引用类型,引用类型需要整体赋值才能触发更新

  2. @Prop:父子组件单向数据传递,子组件只能读取不能修改,父组件更新会同步到子组件

  3. @Link :父子组件双向数据绑定,必须使用$$操作符传递引用,任何一方修改都会同步

  4. @Provide/@Consume:跨组件层级数据共享,无需逐层传递,支持一对多和双向绑定

装饰器选择指南

场景 推荐装饰器 示例
组件内部状态 @State 按钮点击次数、输入框内容
父传子(只读) @Prop 列表项数据、配置信息
父子双向绑定 @Link 表单输入、开关状态
跨层级共享 @Provide/@Consume 主题、语言、用户信息

下期预告

第4篇:数据持久化与网络请求全攻略:Preferences、关系数据库、HTTP实战

在下一篇文章中,我们将深入探讨:

  • Preferences轻量级存储:如何保存用户设置、配置信息

  • 关系型数据库(RDB):复杂数据的本地存储方案

  • HTTP网络请求:封装通用的网络请求工具类

  • 数据缓存策略:提升应用性能的关键技巧

  • 完整项目实战:结合状态管理和数据持久化,构建一个完整的新闻阅读应用


九、参考资料


如果这篇文章对你有帮助,请点赞、收藏、关注支持!你的支持是我持续创作的动力!

有问题欢迎在评论区讨论,我会及时回复!


鸿蒙NEXT开发实战系列 -- 全部文章


标签:鸿蒙NEXT | HarmonyOS NEXT | ArkUI | ArkTS | DevEco Studio | 状态管理 | @State | @Prop | @Link | @Provide | @Consume | 声明式UI | 数据绑定

相关推荐
eastyuxiao5 小时前
如何培养适应AI时代的就业技能?
人工智能
是Dream呀5 小时前
2 分 44 秒,我给一个连招牌都没有的老板娘做了官网
人工智能·trae·solo
小小测试开发5 小时前
AI 编程工具深度实测:Claude Code vs Cursor vs Copilot vs 通义灵码
人工智能·copilot
甲维斯5 小时前
98%命中率!Claude+Opus4.7也太强了吧!
人工智能·ai编程
Pushkin.5 小时前
ReAct 架构深度解析:让大模型学会“边想边做“
人工智能
GIOTTO情5 小时前
媒介投放全链路技术解析:Infoseek 字节探索如何用 AI 重构投放体系
人工智能·重构
123_不打狼5 小时前
神经网络的反向传播(BP)详解
人工智能·神经网络·机器学习
IPHWT 零软网络5 小时前
AI Agent知识库功能解析:多源接入、动态更新与智能检索的技术价值
人工智能·科技·知识库
绿蕉5 小时前
端到端自动驾驶:系统架构的演进与未来
人工智能·系统架构·自动驾驶
aqi005 小时前
一文速览 HarmonyOS 6.0.1 引入的十个新特性
android·华为·harmonyos·鸿蒙·harmony