ArkTS 状态管理选型实操:一个待办清单里的 @State、@Link 与 @Observed
引言:状态管理是鸿蒙入门的第一道坎
写第一个鸿蒙应用时,大多数人不是卡在界面布局上,而是卡在“数据变了,界面为什么不动”上。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 的状态装饰器没有银弹,关键在“谁拥有数据、谁需要改数据”这两个问题上。先想清数据的归属和流向,再挑装饰器,而不是先写代码、出了刷新问题再回来补同步逻辑。一个待办清单写下来,最大的收获不是界面本身,而是这套选型直觉:状态放得越靠近使用它的地方,代码越不容易失控。
- 点赞
- 收藏
- 关注作者
评论(0)