HarmonyOS ArkTS 状态管理实践:@State、@Prop、@Link 与 @Observed 的取舍

举报
yd_210205184 发表于 2026/11/07 09:24:01 2026/11/07
【摘要】 从 @State 到 @ObjectLink,梳理 ArkTS 状态管理的可观察性规则,并给出父子传值、深层对象观察与跨层级共享的取舍建议。

一、为什么单独聊状态管理

在 ArkTS 里写页面,最容易出问题的不是布局,而是「数据改了界面没变」。根因通常只有一个:你改的那个变量,不在框架的观察范围内。

ArkTS 的状态管理本质上是一套「可观察性」规则:只有被装饰器标记过、并且按规则赋值的变量,变化才会触发 UI 重建。搞清楚这条线,绝大多数「不刷新」问题都能自己定位。

二、@State:组件自己的状态

@State 是最基础的一层,特点有三个:

  • 私有:只能在当前组件内部初始化,父组件不能直接给它赋值(只能通过构造参数初始化一次)。
  • 本地:生命周期跟随组件,组件销毁即释放。
  • 可观察:赋值动作会触发该组件重建。
@Entry
@Component
struct Counter {
  @State count: number = 0;

  build() {
    Column() {
      Text(`count = ${this.count}`)
      Button('+1').onClick(() => { this.count++ })
    }
  }
}

关键点在「赋值动作」四个字:this.count++ 是重新赋值,能触发刷新;而 this.obj.a = 1(修改对象内部属性)不会触发,因为 @State 观察的是 obj 这个引用,不是它的字段。这就是后面 @Observed 要解决的问题。

三、@Prop 与 @Link:父子之间怎么传

父子传值只有两条路,取舍其实很清楚:

装饰器 方向 子组件修改 适用场景
@Prop 父 → 子 单向 允许,但不回写父 子组件只读展示,需要本地临时状态
@Link 父 ↔ 子 双向 会同步回父组件 表单、开关、步进器等需要回写的控件

@Prop 是值拷贝:父组件状态变化会重新同步给子组件,但子组件改了不会影响父组件。适合「父组件给一份数据,子组件自己玩」的场景。

@Link 是引用共享:子组件必须用 $ 前缀绑定父组件的 @State。

@Component
struct Stepper {
  @Link value: number;          // 双向
  @Prop step: number = 1;       // 单向

  build() {
    Row() {
      Button('-').onClick(() => { this.value -= this.step })
      Text(`${this.value}`)
      Button('+').onClick(() => { this.value += this.step })
    }
  }
}

@Entry
@Component
struct Page {
  @State total: number = 0;

  build() {
    Column() {
      Text(`总计 ${this.total}`)
      Stepper({ value: $total, step: 2 })
    }
  }
}

一个常见的踩坑:把 @Link 用在深层嵌套的对象上。由于 @Link 同样只观察引用,父组件里对对象字段的修改未必能同步过来,这时应该配合 @Observed 使用。

四、@Observed + @ObjectLink:观察嵌套对象

当状态是 class 实例、并且需要观察它的属性变化时,两步走:

  1. 给类加 @Observed,让框架能代理它的字段;
  2. 子组件用 @ObjectLink 接收这个实例。
@Observed
class User {
  name: string = '';
  age: number = 0;
}

@Component
struct UserCard {
  @ObjectLink user: User;

  build() {
    Column() {
      Text(this.user.name)
      Button('改名').onClick(() => { this.user.name = 'new name' })
    }
  }
}

注意 @ObjectLink 只能接收 @Observed 修饰的类实例,不能接收普通对象字面量,也不能接收 undefined。这是编译期就会报错的硬约束。

五、跨层级:@Provide / @Consume

层级深了以后,一层层透传 @Prop 会很难维护。@Provide / @Consume 就是给这种场景准备的:祖先提供,任意层级后代消费,按变量名匹配,不需要中间组件参与。

@Entry
@Component
struct Root {
  @Provide('theme') theme: string = 'light';
  build() { PageBody() }
}

@Component
struct PageBody {
  @Consume('theme') theme: string;
  build() { Text(this.theme) }
}

代价是可读性:变量名一改,消费端就断了,而且从消费端反向找不到提供方。层级不深时,老老实实传参反而更好维护。

六、几条实践建议

  1. 能用 @State 就别上全局。AppStorage / LocalStorage 适合真正的跨页面共享(登录态、主题),不要当成懒人通道。
  2. 状态尽量扁平。嵌套越深,可观察性越难保证,维护成本呈指数上升。
  3. @Watch 只做副作用,不要在回调里再改回被观察的变量,很容易写出循环更新。
  4. API 12 之后再看 V2。@ObservedV2 + @Trace 的深度观察能力更强,但 V1 / V2 不能在同一组件里混用,迁移要成体系地做,不建议新旧混写。

七、小结

一句话记住取舍:

@State 管自己,@Prop 单向给,@Link 双向改,@Observed + @ObjectLink 管对象内部,@Provide / @Consume 管跨层级。

把「谁持有状态、谁只读、谁需要回写」这三件事在写代码前想清楚,状态管理基本不会成为返工点。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。