彻底搞懂 ArkTS 状态管理 V1:8 个核心装饰器全解析

在 HarmonyOS/OpenHarmony 的声明式 UI 开发中,状态管理是最核心、最基础也是最容易踩坑的部分。很多开发者刚接触 ArkTS 时,都会被 @State、@Prop、@Link 等一堆装饰器搞得晕头转向。

本文完全基于鸿蒙官方最新的状态管理 V1 文档,用最清晰的结构、最准确的定义和最实用的示例,带你一次性搞懂所有 8 个核心状态装饰器。

一、什么是状态管理 V1?

ArkTS 声明式 UI 的核心思想是 “数据驱动 UI”:UI 是状态的映射,状态变化时,UI 会自动重新渲染。

鸿蒙官方提供的状态管理 V1是一套基于装饰器的轻量级状态管理方案,专门解决以下三个核心问题:

  1. 数据所有权:谁拥有数据,谁负责初始化和销毁
  2. 数据传递:数据如何在组件之间传递
  3. 数据响应:数据变化后如何触发 UI 刷新和自定义逻辑

状态管理 V1 是鸿蒙开发中 90% 场景下的首选方案,它简单、高效、开箱即用,不需要引入任何第三方库。

二、基础状态管理:组件内与父子组件

这是你每天都会用到的最基础的状态管理能力,解决 “组件自己用” 和 “父子组件之间传值” 的问题。

2.1 @State:组件内状态

官方定义:@State 装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。

核心特性

  • 拥有数据:数据是组件私有的,组件销毁时数据一起销毁
  • 必须初始化:声明时必须赋予初始值
  • 双向绑定:变量变化自动刷新 UI,UI 输入也能自动更新变量
  • 观察范围:基本类型、对象引用的变化

使用示例

@Entry
@Component
struct Counter {
  // 组件私有状态,必须初始化
  @State count: number = 0

  build() {
    Column() {
      Text(`当前计数:${this.count}`)
      Button("+1")
        .onClick(() => this.count++)
    }
  }
}

2.2 @Prop:父子单向同步

官方定义:@Prop 装饰的变量可以和父组件建立单向同步关系,@Prop 装饰的变量是可变的,但修改不会同步回父组件。

核心特性

  • 不拥有数据:数据来自父组件
  • 不能初始化:必须由父组件传入
  • ➡️ 单向同步:父组件变化自动同步到子组件,子组件变化不会同步回父组件
  • 子组件可修改:子组件可以修改,但修改只在子组件内部生效

使用示例

// 父组件
@Entry
@Component
struct Parent {
  @State message: string = "Hello from Parent"

  build() {
    Column() {
      Text(`父组件:${this.message}`)
      // 父组件将数据传给子组件
      Child({ message: this.message })
    }
  }
}

// 子组件
@Component
struct Child {
  // 子组件接收数据,不能初始化
  @Prop message: string

  build() {
    Column() {
      Text(`子组件:${this.message}`)
      Button("修改消息")
        .onClick(() => {
          // 子组件可以修改,但不会影响父组件
          this.message = "Hello from Child"
        })
    }
  }
}

2.3 @Link:父子双向同步

官方定义:@Link 装饰的变量可以和父组件建立双向同步关系,子组件中 @Link 装饰变量的修改会同步给父组件中建立双向数据绑定的数据源,父组件的更新也会同步给 @Link 装饰的变量。

核心特性

  • 不拥有数据:数据来自父组件
  • 不能初始化:必须由父组件传入
  • ↔️ 双向同步:父子组件任何一方修改,另一方都会自动同步
  • 子组件可修改:子组件修改会直接影响父组件

使用示例

// 父组件
@Entry
@Component
struct Parent {
  @State count: number = 0

  build() {
    Column() {
      Text(`父组件:${this.count}`)
      // 父组件使用$语法传递双向绑定
      Child({ count: $count })
    }
  }
}

// 子组件
@Component
struct Child {
  // 子组件接收双向绑定数据
  @Link count: number

  build() {
    Column() {
      Text(`子组件:${this.count}`)
      Button("+1")
        .onClick(() => {
          // 子组件修改会同步回父组件
          this.count++
        })
    }
  }
}

三、跨层级状态管理:@Provide/@Consume

当组件层级超过 3 层时,用 @Link 一层一层传参就会变得非常繁琐,中间组件被迫成为 “传话筒”。这时候就需要跨层级状态管理。

官方定义:@Provide/@Consume 装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过 alias(别名)或者属性名绑定。

核心特性

  • @Provide 拥有数据:只有提供数据的组件拥有数据
  • @Consume 不拥有数据:后代组件直接消费数据
  • ↔️ 双向同步:任何地方修改,所有使用该数据的地方都会自动刷新
  • 🚫 中间组件无需参与:数据直接从祖先组件传递到后代组件

使用示例

// 爷爷组件:提供数据
@Entry
@Component
struct Grandfather {
  // 提供数据,所有后代都能拿到
  @Provide("theme") theme: string = "light"

  build() {
    Column() {
      Text(`爷爷:当前主题是${this.theme}`)
      Button("切换主题")
        .onClick(() => {
          this.theme = this.theme === "light" ? "dark" : "light"
        })
      
      // 什么都不用传给爸爸
      Father()
    }
  }
}

// 爸爸组件:完全不需要管这个数据
@Component
struct Father {
  build() {
    Column() {
      Text("我是爸爸,我什么都没传")
      // 什么都不用传给儿子
      Son()
    }
  }
}

// 儿子组件:直接消费数据
@Component
struct Son {
  // 通过别名直接消费数据
  @Consume("theme") theme: string

  build() {
    Text(`儿子:当前主题是${this.theme}`)
  }
}

四、复杂对象状态管理:@Observed/@ObjectLink

这是鸿蒙状态管理中最容易踩坑的部分。基础状态装饰器只能观察对象引用的变化,不能观察对象内部属性的变化,更不能观察嵌套对象的属性变化。

4.1 @Observed:标记可观察类

官方定义:@Observed 装饰 class,需要观察多层嵌套场景的 class 需要被 @Observed 装饰。单独使用 @Observed 没有任何作用,需要和 @ObjectLink、@Prop 联用。

核心特性

  • 只能装饰类:不能装饰变量
  • 开启深度观察:告诉 ArkTS 这个类的实例需要被深度观察
  • 单独使用无效:必须和 @ObjectLink 或 @Prop 联用

4.2 @ObjectLink:观察对象内部属性

官方定义:@ObjectLink 装饰的变量接收 @Observed 装饰的 class 的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。

核心特性

  • 不拥有数据:数据来自父组件
  • 不能初始化:必须由父组件传入
  • ↔️ 双向同步:子组件修改对象内部属性,父组件也会同步
  • 观察范围:对象内部任意属性的变化,包括嵌套对象

使用示例

// 第一步:给所有需要观察的类加上@Observed
@Observed
class User {
  name: string
  address: Address

  constructor(name: string, city: string) {
    this.name = name
    this.address = new Address(city)
  }
}

@Observed
class Address {
  city: string
  constructor(city: string) {
    this.city = city
  }
}

// 父组件
@Entry
@Component
struct Parent {
  @State user: User = new User("张三", "北京")

  build() {
    Column() {
      Text(`父:${this.user.name},住在${this.user.address.city}`)
      Button("修改城市为上海")
        .onClick(() => {
          // 现在修改嵌套对象属性,UI会刷新了
          this.user.address.city = "上海"
        })
      
      Child({ user: this.user })
    }
  }
}

// 子组件
@Component
struct Child {
  // 用@ObjectLink代替@Link
  @ObjectLink user: User

  build() {
    Column() {
      Text(`子:${this.user.name},住在${this.user.address.city}`)
      Button("修改城市为广州")
        .onClick(() => {
          // 子组件修改,父组件也会同步刷新
          this.user.address.city = "广州"
        })
    }
  }
}

五、状态变化监听:@Watch

前面的所有装饰器都只解决 “数据变化→UI 刷新” 的问题,而 @Watch 解决 “数据变化→执行自定义逻辑” 的问题。

官方定义:@Watch 应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。

核心特性

  • 不拥有数据:只是状态变化的观察者
  • 自动执行:当被监听的变量变化时,自动调用回调函数
  • 严格相等判断:使用严格相等(===)来判断值是否变化
  • 执行时机:在 UI 刷新之前执行

使用示例

@Entry
@Component
struct ShoppingCart {
  // 监听单价和数量的变化
  @Watch('updateTotal')
  @State price: number = 10

  @Watch('updateTotal')
  @State quantity: number = 1

  // 总价是衍生值
  total: number = 10

  build() {
    Column() {
      Text(`单价:${this.price}`)
      Text(`数量:${this.quantity}`)
      Text(`总价:${this.total}`)
      
      Button("单价+1")
        .onClick(() => this.price++)
      
      Button("数量+1")
        .onClick(() => this.quantity++)
    }
  }

  // 只要单价或数量变化,总价就会自动更新
  updateTotal() {
    this.total = this.price * this.quantity
    console.log(`总价更新为:${this.total}`)
  }
}

重要注意事项

  • 只能监听被状态装饰器装饰的变量
  • 绝对不要在回调里修改被监听的变量本身,否则会导致无限循环
  • 回调函数的参数依次是新值和旧值

六、所有装饰器终极对比表

装饰器 装饰对象 数据所有权 数据方向 能否修改 适用场景
@State 组件属性 自己拥有 内部 可改 组件私有状态
@Prop 组件属性 父拥有 父→子单向 可改但不同步 只给子组件展示的数据
@Link 组件属性 父拥有 父子双向 可改 简单父子互相修改数据
@Provide 组件属性 自己拥有 全局双向 可改 多层组件共享同一个状态
@Consume 组件属性 祖先拥有 全局双向 可改 后代组件使用共享状态
@Observed - - - 复杂对象、嵌套对象的类
@ObjectLink 组件属性 父拥有 双向 可改 子组件修改嵌套对象属性
@Watch 组件属性 - - - 数据变化时执行自定义逻辑

七、最佳实践建议

  1. 优先使用 @State:组件内部的状态优先使用 @State,保持状态的私有性
  2. 单向优先:如果子组件不需要修改数据,优先使用 @Prop 而不是 @Link
  3. 避免滥用 @Provide/@Consume:只有当组件层级超过 3 层时才使用,否则会降低代码的可读性
  4. 复杂对象必须加 @Observed:只要是需要观察内部属性变化的类,都要加上 @Observed 装饰器
  5. @Watch 用于衍生值和副作用:不要在 @Watch 里修改被监听的变量本身

八、总结

本文基于鸿蒙官方文档,全面解析了 ArkTS 状态管理 V1 的 8 个核心装饰器。这些装饰器覆盖了鸿蒙开发中 90% 以上的状态管理场景,是每个鸿蒙开发者必须掌握的基础知识。

掌握了这些装饰器,你就能写出清晰、高效、可维护的声明式 UI 代码。如果你想深入学习,可以参考鸿蒙官方的状态管理 V1 文档,里面有更详细的说明和更多示例。

Logo

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

更多推荐