用 ArkTS 的 @Observed 与 @ObjectLink 管理鸿蒙应用的跨页面共享状态
在鸿蒙应用开发中,页面之间共享一份可变状态是最常见的需求之一。比如一个单词打卡应用:首页要显示今日已背单词数,单词详情页要修改掌握程度,统计页要汇总本周打卡情况,三处读写的其实是同一份数据。如果每个页面各自维护一份状态,数据很快就会互相打架。本文结合一个小型实践,聊聊 ArkTS 中 @Observed 与 @ObjectLink 这对组合的正确用法。
一、问题从哪里来
ArkTS 的 @State 只能管理当前组件内部的状态,子组件想修改父组件的数据,一般用 @Link。但 @Link 有一个限制:它只能同步一层数据。当状态是一个对象、且要修改的是对象内部的属性时,@Link 就无能为力了。更麻烦的是页面跳转:用 router.pushUri 跳到新页面后,@Link 链路断开,新页面拿到的是数据的快照而不是引用,改了也不会回写。
二、@Observed 与 @ObjectLink 的分工
@Observed 修饰一个类,表示这个类的实例是可观察的,实例内部属性的变化可以被 UI 感知。@ObjectLink 则用在子组件或子页面中,接收一个 @Observed 类型的对象,并与之建立引用关系。两者分工很明确:@Observed 负责让数据可观察,@ObjectLink 负责让多处共享同一份引用。
@Observed
class WordBook {
todayCount: number = 0;
mastered: string[] = [];
markMastered(word: string) {
if (!this.mastered.includes(word)) {
this.mastered.push(word);
this.todayCount++;
}
}
}
注意 markMastered 把“去重”和“计数”收敛到数据类内部,而不是散落在各个页面里。这样无论多少页面共享这份数据,业务规则都只有一份。
三、跨页面共享的完整写法
入口页面持有这份数据的唯一实例,子组件和子页面只持有引用。
@Entry
@Component
struct HomePage {
@State book: WordBook = new WordBook();
build() {
Column() {
Text(`今日已背 ${this.book.todayCount} 个单词`)
WordCard({ book: this.book })
}
}
}
@Component
struct WordCard {
@ObjectLink book: WordBook;
build() {
Button('掌握了').onClick(() => {
this.book.markMastered('abandon');
})
}
}
跨页面时,把同一个 book 实例通过路由参数的共享对象或全局单例传递过去,子页面同样用 @ObjectLink 接收。点击“掌握了”之后,首页的计数会立刻刷新,统计页读取到的也是更新后的结果,因为它们指向的是同一个 WordBook 实例。
四、容易踩的三个坑
第一,只给外层加 @State,不给数据类加 @Observed。此时对象引用变化能刷新,对象内部属性变化却不会刷新,表现为改了数据界面不动。
第二,在子组件里给 @ObjectLink 修饰的变量重新赋值。@ObjectLink 建立的是引用关系,重新赋值会断开与源数据的链接,之后再改就互不影响了。正确做法是只修改对象的属性,或调用对象的方法。
第三,在不需要共享的场景滥用这套组合。如果状态只在单个组件内部使用,直接用 @State 就够了。共享状态的层级越少,数据流向越清晰,出了问题也越容易定位。
五、小结
@Observed 让对象内部的变化可被观察,@ObjectLink 让多个组件和页面共享同一份引用。把状态的唯一来源放在一处、把修改规则收进数据类内部,页面之间就不再需要层层透传回调。下一篇文章,我会继续用这个单词打卡应用为例,聊聊如何用 Preferences 把这份共享状态持久化到本地,让打卡记录在应用重启后依然存在。
- 点赞
- 收藏
- 关注作者
评论(0)