ArkTS 状态管理选型实操:一个待办清单里的 @State、@Link 与 @Observed

举报
yd_268363280 发表于 2026/11/08 10:04:00 2026/11/08
【摘要】 引言:状态管理是鸿蒙入门的第一道坎写第一个鸿蒙应用时,大多数人不是卡在界面布局上,而是卡在“数据变了,界面为什么不动”上。ArkTS 的装饰器看起来不多,@State、@Prop、@Link、@Observed、@ObjectLink 几个名字又很像,选错一个,轻则多写一堆同步代码,重则出现改了数据界面却不刷新的怪现象。这篇笔记用一个待办清单(Todo)小应用做载体,把这几个装饰器的分工和...

引言:状态管理是鸿蒙入门的第一道坎

写第一个鸿蒙应用时,大多数人不是卡在界面布局上,而是卡在“数据变了,界面为什么不动”上。ArkTS 的装饰器看起来不多,@State、@Prop、@Link、@Observed、@ObjectLink 几个名字又很像,选错一个,轻则多写一堆同步代码,重则出现改了数据界面却不刷新的怪现象。

这篇笔记用一个待办清单(Todo)小应用做载体,把这几个装饰器的分工和三个真实踩过的坑讲清楚。结论先说在前面:组件内部自己用的状态归 @State,父子之间要同步的归 @Link,跨多层共享的复杂对象归 @Observed + @ObjectLink,选型错了再补代码也救不回来。

一、最小闭环:@State 管组件自己的状态

Todo 列表页最简单的状态是“输入框里的文字”和“是否显示已完成”。这两个状态只在当前组件内部流转,不需要传给任何子组件,用 @State 就够了:

@Entry
@Component
struct TodoPage {
  @State inputText: string = ''
  @State showDone: boolean = true
  @State todos: TodoItem[] = []

  build() {
    Column() {
      TextInput({ text: this.inputText })
        .onChange((value: string) => {
          this.inputText = value
        })
      // ... 列表渲染
    }
  }
}

@State 的规则很直接:它装饰的变量归当前组件所有,变量变化时框架自动刷新用到它的 UI。初学者常犯的错误是把什么都往 @State 里塞,包括子组件也要改的数据——一旦子组件也需要改这份数据,@State 就不够用了,因为它不支持跨组件写回。

二、父子同步:@Prop 单向,@Link 双向

Todo 的每一行是一个子组件 TodoRow,行里有个复选框,点一下要把“已完成”写回父组件的数组。父子之间传这个状态,有两个选择:

@Prop 是单向同步。父组件改了,子组件跟着变;但子组件自己改了,不会回写给父组件。如果把复选框状态用 @Prop 传进去,子组件里点选框时必须再通过回调函数通知父组件去改,否则点完界面看似变了,父组件的数据还是旧的,下次刷新就“复原”。

@Link 是双向同步。父子共用同一份数据源,任何一方改了,另一方和 UI 一起刷新。Todo 行的完成状态就应该用 @Link:

@Component
struct TodoRow {
  @Link isDone: boolean
  @Prop title: string

  build() {
    Row() {
      Checkbox({ name: 'done', group: 'todo' })
        .select(this.isDone)
        .onChange((value: boolean) => {
          this.isDone = value // 直接写回父组件
        })
      Text(this.title)
    }
  }
}

选型口诀很简单:子组件只读,用 @Prop;子组件也要改,用 @Link。标题这种纯展示字段放 @Prop,勾选状态这种要回写的放 @Link,分工一旦定下来,后续几乎不会返工。

三、跨层共享:@Observed + @ObjectLink 管复杂对象

当 TodoItem 本身是个对象,里面有标题、完成状态、优先级、截止日期好几个字段,而且要在列表页、详情页、统计页三个地方共享时,单个布尔值的 @Link 就不够用了。这时用 @Observed 装饰数据类,再用 @ObjectLink 在组件里引用它:

@Observed
class TodoItem {
  title: string = ''
  isDone: boolean = false
  priority: number = 1
  constructor(title: string) {
    this.title = title
  }
}

@Observed 让对象的属性变化能被框架观测到,@ObjectLink 则让不同组件拿到的是同一个对象的引用。一处改优先级,列表页的排序徽标和详情页的展示会一起刷新,不用再写事件总线或者层层回调。这个组合是鸿蒙里做“多页面共享同一份业务对象”的标准做法。

四、三个实测踩过的坑

第一个坑:@Prop 里改数据不回写。早期我把勾选状态用 @Prop 传给子组件,子组件里直接改 this.isDone,编译不报错,点选框当时也变了,但一切回列表页就复原——因为 @Prop 的修改只停留在子组件本地。改成 @Link 后问题消失。教训是:凡是子组件要写回的状态,一律 @Link,不要抱侥幸心理。

第二个坑:数组元素替换要整体赋值。@State 装饰的数组,直接改 this.todos[0].isDone = true 有时不触发刷新,因为框架观测的是数组引用而不是数组里每个对象的每个字段。稳妥的写法是替换整个元素或重新赋值数组:this.todos[0] = newItem 或者 this.todos = [...this.todos]。如果数组元素本身是 @Observed 类,配合 @ObjectLink 使用,属性级修改就能正常刷新。

第三个坑:@Observed 嵌套对象变更不刷新。TodoItem 里如果还嵌套了一个 meta: TaskMeta 对象,只给外层加 @Observed 是不够的,内层对象属性变化时框架观测不到。正确做法是内层类同样加 @Observed,或者把嵌套字段拍平。嵌套越深越要小心,数据模型设计时就应尽量扁平。

五、选型速查表

场景 装饰器
只在当前组件内用 @State
父传子,子组件只读 @Prop
父子双向同步 @Link
多组件共享复杂对象 @Observed + @ObjectLink
跨页面、跨 Ability 共享 AppStorage / PersistentStorage

这张表贴在工位上,写新页面之前先对一下,基本不会再选错。

结语

ArkTS 的状态装饰器没有银弹,关键在“谁拥有数据、谁需要改数据”这两个问题上。先想清数据的归属和流向,再挑装饰器,而不是先写代码、出了刷新问题再回来补同步逻辑。一个待办清单写下来,最大的收获不是界面本身,而是这套选型直觉:状态放得越靠近使用它的地方,代码越不容易失控。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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