news 2026/10/23 22:04:08

【ArkUI 练中学】第6课:状态管理进阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【ArkUI 练中学】第6课:状态管理进阶

本节目标

· 理解 @State 在嵌套对象场景下的局限性,掌握 @Observed 与 @ObjectLink 配合实现深度监听的方法
· 掌握 @Watch 装饰器监听状态变量变化的能力,理解其严格相等触发机制及使用场景
· 掌握 AppStorage 应用级全局状态存储的读写方法,理解 @StorageProp 与 @StorageLink 的同步机制
· 掌握 LocalStorage 页面级状态存储的使用方式,区分 @LocalStorageProp 与 @LocalStorageLink
· 了解 @Track 实现类对象属性级精准更新的优化手段
· 理解状态管理 V1 与 V2 的核心区别及适用场景
· 能够根据数据的作用范围(组件内、页面内、应用级)和数据类型(简单类型、嵌套对象、数组)选择合适的装饰器组合

一、@Observed 与 @ObjectLink:嵌套对象深度监听

1.1 @State 的局限性

在之前的学习中,@State 已经能够满足简单类型和第一层属性的状态驱动需求。但当数据是嵌套对象或对象数组时,@State 就显得力不从心了。

具体来说,@State 管理数组本身的变化(增删改),但仅能观察对象的第一层属性变化。如果要深度观察,需要传递给 @ObjectLink。例如,以下场景中修改嵌套对象的深层属性,UI 不会刷新:

classAddress{city:string='北京'district:string='海淀区'}classUser{name:string='张三'address:Address=newAddress()}@Entry@Componentstruct Index{@Stateuser:User=newUser()build(){Column(){Text(`城市:${this.user.address.city}`)// 修改 city 不会刷新Button('改城市').onClick(()=>{this.user.address.city='上海'// 不会触发 UI 更新})}}}

1.2 @Observed 标记可观察类

@Observed 用于标记一个类为可观察的类,使其属性变化能够被框架观察到。当一个类被 @Observed 装饰后,该类的实例在被 @ObjectLink 接收时,其属性的变化会触发关联 UI 的更新。

@ObservedclassAddress{city:string='北京'district:string='海淀区'}

关键规则:@Observed 只能装饰 class,不能装饰 struct 或接口。类中需要被观察的属性不需要额外装饰,@Observed 会使该类的所有实例属性都变为可观察的。

1.3 @ObjectLink 在子组件中绑定对象

@ObjectLink 只能接收被 @Observed 装饰的 class 实例,用于在子组件中建立对该对象的深度监听。其工作机制是:父组件通过 @State 持有被 @Observed 装饰的对象实例,将嵌套的子对象通过组件参数传递给子组件,子组件使用 @ObjectLink 接收同一个实例,从而建立属性级别的双向数据同步。

完整的嵌套对象监听示例:

// Address.ets@ObservedexportclassAddress{city:string=''constructor(city:string){this.city=city}}// User.ets@ObservedexportclassUser{name:string=''address:Addressconstructor(name:string,city:string){this.name=namethis.address=newAddress(city)}}
// AddressView.ets — 子组件,用 @ObjectLink 接收嵌套对象import{Address}from'./Address'@Componentexportstruct AddressView{@ObjectLinkaddress:Address// 接收 @Observed 实例,建立深度监听build(){Row(){Text(`城市:${this.address.city}`).fontSize(16)Button('切换城市').onClick(()=>{this.address.city=this.address.city==='北京'?'上海':'北京'})}}}
// Index.ets — 父组件import{User}from'./User'import{AddressView}from'./AddressView'@Entry@Componentstruct Index{@Stateuser:User=newUser('张三','北京')build(){Column({space:20}){Text(`用户:${this.user.name}`).fontSize(20)// 将嵌套对象 address 传递给子组件AddressView({address:this.user.address})}.width('100%').padding(20)}}

点击子组件中的按钮修改 address.city 后,Text 组件会自动刷新。这是因为 @ObjectLink 观察的是 @Observed 实例的属性变化,修改 city 会触发子组件的重新渲染。

@ObjectLink 的使用约束:

· @ObjectLink 必须由父组件传入 @Observed 实例,不能本地初始化。
· @ObjectLink 只能监听代理对象的第一层属性,如果是三层嵌套对象,需要一层一层写组件,每一层都配上 @ObjectLink。
· @ObjectLink 不会深拷贝数据,能显著降低组件创建时的性能消耗。

二、@Watch:状态变量变化监听

2.1 @Watch 的基本用法

如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。当状态变量变化时,@Watch 的回调方法将被调用。

@Watch 在 ArkUI 框架内部判断数值有无更新使用的是严格相等(===),当严格相等判断的结果是 false(即不相等)时,就会触发 @Watch 的回调。

@Entry@Componentstruct WatchExample{@State@Watch('onCountChange')count:number=0@Statemessage:string=''onCountChange(changedPropertyName:string):void{this.message=`count 从${this.count-1}变为${this.count}`}build(){Column({space:20}){Text(`当前计数:${this.count}`).fontSize(24)Text(this.message).fontSize(16).fontColor(Color.Gray)Button('增加').onClick(()=>{this.count++})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}

2.2 使用 changedPropertyName 进行不同逻辑处理

在多个状态变量绑定同一个 @Watch 的回调方法时,可以通过 changedPropertyName 参数进行不同的逻辑处理——将属性名作为字符串输入参数,不返回任何内容。

@Entry@Componentstruct MultiWatch{@State@Watch('onAnyChange')name:string=''@State@Watch('onAnyChange')age:number=0onAnyChange(propName:string):void{if(propName==='name'){console.log('name 变化了')}elseif(propName==='age'){console.log('age 变化了')}}build(){Column(){TextInput({placeholder:'输入姓名'}).onChange((value:string)=>{this.name=value})Button('增加年龄').onClick(()=>{this.age++})}}}

2.3 @Watch 的注意事项

· @Watch 可以监听所有装饰器装饰的状态变量,但不允许监听常规变量。
· 装饰器顺序不影响实际功能,建议 @State、@Prop、@Link 等装饰器在 @Watch 之前,以保持整体风格的一致。
· 回调触发时间是变量真正变化、被赋值的时间。
· 不建议在 @Watch 函数中调用 async/await 等异步操作,会影响性能。

三、AppStorage:应用级全局状态存储

3.1 AppStorage 的定位

AppStorage 是与应用进程绑定的全局 UI 状态存储中心,由 UI 框架在应用启动时创建,将 UI 状态数据存储于运行内存,实现应用级全局状态共享。AppStorage 中的属性可以被双向同步,它和应用的进程绑定,生命周期跟随应用进程。

3.2 基本 API 与使用方式

// 设置或创建属性AppStorage.setOrCreate('themeColor','#007DFF')AppStorage.setOrCreate('fontSize',16)// 获取属性constcolor:string=AppStorage.get<string>('themeColor')??'#000000'// 删除属性AppStorage.delete('fontSize')

3.3 @StorageProp 与 @StorageLink

组件中通过 @StorageProp 和 @StorageLink 装饰器来建立与 AppStorage 中属性的联系:

· @StorageProp:与 AppStorage 建立单向同步。AppStorage 中的属性变化会同步到组件,但组件本地的修改不会同步回 AppStorage。
· @StorageLink:与 AppStorage 建立双向同步。组件中对变量的修改会同步回 AppStorage,AppStorage 中的变化也会同步到组件。

@Entry@Componentstruct ThemeExample{@StorageLink('themeColor')themeColor:string='#007DFF'@StorageProp('fontSize')fontSize:number=16build(){Column({space:20}){Text('主题预览').fontSize(this.fontSize).fontColor(this.themeColor)Button('切换主题色').onClick(()=>{// @StorageLink 双向同步,修改会写入 AppStoragethis.themeColor=this.themeColor==='#007DFF'?'#FF5500':'#007DFF'})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}

3.4 PersistentStorage:持久化存储

PersistentStorage 提供了状态变量持久化的能力,通常和 AppStorage 配合使用,选择 AppStorage 存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。

// 在 UI 实例初始化成功后调用PersistentStorage.persistProp('themeColor','#007DFF')

注意事项:PersistentStorage 的持久化变量最好小于 2KB,因为写入磁盘是在 UI 线程同步执行的,大量数据本地读写会影响 UI 渲染性能。PersistentStorage 和 UI 实例相关联,持久化操作需要在 UI 实例初始化成功后才可以被调用。

四、LocalStorage:页面级状态存储

4.1 LocalStorage 的定位

LocalStorage 是页面级的 UI 状态存储,是 ArkTS 为构建页面级别状态变量提供存储的内存内的“数据库”。应用程序可以创建多个 LocalStorage 实例,实例可以在页面内共享,也可以通过 getSharedLocalStorage 接口实现 UIAbility 内跨页面共享。跨 UIAbility 共享数据推荐使用 AppStorage。

4.2 @LocalStorageProp 与 @LocalStorageLink

· @LocalStorageProp:与 LocalStorage 中给定属性建立单向同步。本地修改不会同步回 LocalStorage,但 LocalStorage 中的变化会覆盖本地值。
· @LocalStorageLink:与 LocalStorage 中给定属性建立双向同步。本地修改会同步回 LocalStorage,LocalStorage 的变化也会同步到组件。

// 创建 LocalStorage 实例并初始化数据conststorage=newLocalStorage({'playCount':0})@Entry(storage)@Componentstruct LocalStorageExample{@LocalStorageLink('playCount')playCount:number=0@LocalStorageProp('playCount')playCountProp:number=0build(){Column({space:20}){Text(`播放次数(Link):${this.playCount}`).fontSize(18)Text(`播放次数(Prop):${this.playCountProp}`).fontSize(18)Button('播放 +1').onClick(()=>{// @LocalStorageLink 双向同步,修改会写入 LocalStoragethis.playCount++})Button('通过 API 修改').onClick(()=>{// 直接修改 LocalStorage,两个装饰器都会同步storage.setOrCreate('playCount',this.playCount+10)})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}

4.3 AppStorage 与 LocalStorage 的选型

作用范围:AppStorage 是应用级全局;LocalStorage 是页面级。

生命周期:AppStorage 跟随应用进程;LocalStorage 跟随页面或 UIAbility。

跨 UIAbility:AppStorage 支持;LocalStorage 不支持,需通过 getSharedLocalStorage 在 UIAbility 内共享。

典型场景:AppStorage 适合用户登录态、主题配置、全局设置;LocalStorage 适合页面内临时数据、表单草稿、页面状态。

选型原则:需要在整个应用范围内共享的数据用 AppStorage;只在单个页面或 UIAbility 内共享的数据用 LocalStorage。跨 UIAbility 共享数据推荐使用 AppStorage。

五、@Track:类对象属性级更新

5.1 为什么需要 @Track

在状态管理 V1 中,@State 等装饰器默认支持观察第一层属性的变化,第一层属性的变化虽然可以触发更新,但无法做到类属性级的观察。当一个 class 对象是状态变量时,即使只修改了其中一个属性,所有使用了该对象属性的 UI 都会刷新,产生冗余刷新。

以下示例展示了这一问题:

classInfo{name:string='Jack'age:number=12}@Entry@Componentstruct Index{@Stateinfo:Info=newInfo()build(){Column(){Text(`name:${this.info.name}`).fontSize(30)Text(`age:${this.info.age}`).fontSize(30)Button('change name').onClick(()=>{this.info.name='Jane'// 仅修改 name,但两个 Text 都会刷新})}}}

点击按钮后,即使只修改了 info.name,Text(age: ${this.info.age}) 也会重新渲染,而它并未使用 name 属性,这次刷新是冗余的。

5.2 @Track 的用法

@Track 应用于 class 对象的属性级更新。当被 @Track 装饰的属性发生变化时,只会触发该属性关联的 UI 更新。

classInfo{@Trackname:string='Jack'@Trackage:number=12}@Entry@Componentstruct Index{@Stateinfo:Info=newInfo()build(){Column(){Text(`name:${this.info.name}`).fontSize(30)Text(`age:${this.info.age}`).fontSize(30)Button('change name').onClick(()=>{this.info.name='Jane'// 只有 name 关联的 Text 刷新})}}}

关键规则:如果 class 类中使用了 @Track 装饰器,则未被 @Track 装饰的属性不能在 UI 中使用,否则会发生运行时报错。@Track 不具备深度观测能力,它只管装饰的那一层属性。

六、状态管理 V1 与 V2 对比

6.1 V1 的局限

状态管理 V1 使用代理观察数据,通过 @Observed + @ObjectLink 的组合虽然能够实现对嵌套类中属性变化的观测,但是当嵌套层级较深时,需要一层一层写组件,每一层都配上 @ObjectLink,代码会变得十分复杂,易用性差。

6.2 V2 的核心改进

HarmonyOS 5.0+ 推出了状态管理 V2 版本,使用 @ObservedV2 装饰类、@Trace 装饰成员变量,可以自动深度观测嵌套对象中所有被 @Trace 装饰的属性变化,无需逐层创建子组件。

V1 与 V2 装饰器对应关系如下:

· @Component 对应 @ComponentV2,用于创建自定义组件。
· @State 对应 @Local,用于组件内部状态。
· @Prop 对应 @Param,用于组件外部输入。
· @Link 对应 @Param + @Event,用于双向同步。
· @Observed + @ObjectLink 对应 @ObservedV2 + @Trace,用于深度观测。
· @Provide / @Consume 对应 @Provider / @Consumer,用于跨层级传递。
· @Watch 对应 @Monitor,用于状态变化监听。
· AppStorage 对应 AppStorageV2,用于应用级状态存储。

6.3 混用注意事项

V1 的自定义组件中不可以使用 V2 的装饰器,否则编译报错。当组件间不传递变量时,V1 的自定义组件中可以使用 V2 的自定义组件。

七、多元化习题

习题 1(判断题)

题目:在状态管理 V1 中,@State 装饰器能够自动观察嵌套对象中所有层级属性的变化。

答案:错误

解读:@State 仅能观察对象的第一层属性变化。对于嵌套对象,需要将嵌套的子对象传递给子组件,在子组件中使用 @ObjectLink 接收,并配合 @Observed 装饰数据类,才能实现深度监听。

习题 2(单选题)

题目:以下哪个装饰器用于监听状态变量的变化,当状态变量变化时触发回调函数?

A. @Observed
B. @ObjectLink
C. @Watch
D. @Track

答案:C

解读:@Watch 用于监听状态变量的变化,当状态变量变化时,@Watch 的回调方法将被调用。@Observed 用于标记类可观察,@ObjectLink 用于在子组件中绑定可观察对象,@Track 用于类对象的属性级精准更新。

习题 3(多选题)

题目:关于 AppStorage 和 LocalStorage,以下说法正确的有(多选):

A. AppStorage 是与应用进程绑定的全局 UI 状态存储中心
B. LocalStorage 可以创建多个实例
C. AppStorage 的生命周期跟随页面
D. LocalStorage 支持 UIAbility 内多个页面间状态共享

答案:A、B、D

解读:AppStorage 是与应用进程绑定的全局 UI 状态存储中心,由 UI 框架在应用启动时创建,选项 A 正确。应用程序可以创建多个 LocalStorage 实例,选项 B 正确。AppStorage 的生命周期跟随应用进程,而非页面,选项 C 错误。LocalStorage 支持 UIAbility 实例内多个页面间状态共享,选项 D 正确。

习题 4(代码填空题)

题目:请补全以下代码,使子组件能够深度监听父组件传递的嵌套对象属性变化。

@ObservedclassAddress{city:string='北京'}@Componentstruct AddressView{// 在此处填写装饰器______________ address:Addressbuild(){Text(`城市:${this.address.city}`).onClick(()=>{this.address.city='上海'// 需要触发 UI 刷新})}}

答案:@ObjectLink

解读:@ObjectLink 只能接收被 @Observed 装饰的 class 实例,用于在子组件中建立对该对象的深度监听。父组件通过 @State 持有被 @Observed 装饰的对象实例,将嵌套的子对象传递给子组件,子组件使用 @ObjectLink 接收。

习题 5(代码改错题)

题目:以下代码存在错误,请指出问题并修正。

@Entry@Componentstruct Index{@Statecount:number=0@Watch('onCountChange')message:string=''onCountChange(propName:string):void{console.log(`${propName}变化了`)}build(){Column(){Text(`${this.count}`)Button('+1').onClick(()=>{this.count++})}}}

答案:@Watch 不允许监听常规变量,只能监听被 @State、@Prop、@Link 等装饰器装饰的状态变量。修正如下:

@State@Watch('onCountChange')count:number=0

解读:@Watch 可以监听所有装饰器装饰的状态变量,但不允许监听常规变量。题目中 message 是常规变量,@Watch 无法监听其变化。

习题 6(简答题)

题目:简述 @StorageProp 和 @StorageLink 的区别,以及 @LocalStorageProp 和 @LocalStorageLink 的区别。

答案:@StorageProp 与 AppStorage 建立单向同步,AppStorage 中的属性变化会同步到组件,但组件本地的修改不会同步回 AppStorage。@StorageLink 与 AppStorage 建立双向同步,组件中对变量的修改会同步回 AppStorage,AppStorage 中的变化也会同步到组件。@LocalStorageProp 与 LocalStorage 中给定属性建立单向同步,本地修改不会同步回 LocalStorage,但 LocalStorage 中的变化会覆盖本地值。@LocalStorageLink 与 LocalStorage 中给定属性建立双向同步,本地修改会同步回 LocalStorage,LocalStorage 的变化也会同步到组件。

解读:两者都是“Prop 单向、Link 双向”的设计模式。选择单向还是双向取决于组件是否需要将本地修改回写到存储中。如果组件只需要读取全局状态而不需要修改,使用 Prop 即可;如果组件需要修改并同步给其他使用该状态的组件,则使用 Link。

八、本节知识点总结

@Observed + @ObjectLink
解决 @State 无法深度监听嵌套对象的局限。@Observed 标记数据类为可观察,@ObjectLink 在子组件中绑定对象实例实现属性级深度监听。嵌套层级深时需逐层创建子组件配合使用。

@Watch
为状态变量设置变化监听回调。基于严格相等(===)判断触发,不允许监听常规变量,支持通过 changedPropertyName 区分多个变量共用一个回调的场景。

AppStorage
应用级全局状态存储中心,由 UI 框架在应用启动时创建,生命周期跟随应用进程。通过 @StorageProp 建立单向同步,@StorageLink 建立双向同步。PersistentStorage 配合 AppStorage 实现数据持久化。

LocalStorage
页面级状态存储,支持 UIAbility 内跨页面共享。@LocalStorageProp 单向同步,@LocalStorageLink 双向同步。跨 UIAbility 共享推荐使用 AppStorage。

@Track
类对象属性级精准更新装饰器,减少 @State 观察复杂对象时的冗余刷新。类中使用了 @Track 后,未被装饰的属性不能在 UI 中使用。

状态管理 V1 与 V2
V1 使用代理观察数据,嵌套场景需要逐层使用 @ObjectLink;V2 通过 @ObservedV2 + @Trace 实现自动深度观测,无需逐层创建子组件。V1 和 V2 装饰器不能在同一自定义组件中混用。

选型建议
组件内部简单状态用 @State;父子组件传值用 @Prop / @Link;嵌套对象深度监听用 @Observed + @ObjectLink;跨层级共享用 @Provide / @Consume;页面内共享用 LocalStorage;应用级全局共享用 AppStorage。

下节预告
第7课将进入 ArkUI 动画体系的学习,涵盖属性动画 animateTo、显式动画 animateTo 与 animation、转场动画 transition、路径动画以及组件动画的综合运用,帮助开发者构建流畅的交互体验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 22:03:56

【ArkUI 练中学】第4课:高级布局与列表性能优化

本节目标掌握 Flex 弹性布局的核心属性 flexBasis、flexGrow、flexShrink 及 FlexWrap 换行布局的用法掌握 Grid 网格布局的 columnsTemplate / rowsTemplate 行列设置方法&#xff0c;理解 fr 单位与 GridItem 的配合掌握 Stack 层叠布局的 alignContent 对齐方式与 zIn…

作者头像 李华
网站建设 2026/10/19 6:49:42

三维模型制作01——第一周(10.9)

三维模型制作010 参考资料和备课指南1 今日整体课程安排2 认识3dsmax工作界面2.1 标题栏2.1.1 保存方式2.1.2 讲解键盘2.2 菜单栏2.3 主工具栏2.4 视图区2.5 命令面板区2.6 视图控制区2.7 状态与提示信息栏2.8 动画控制区3 将UI界面复原4 细节注意4.1 单位设置4.2 viewcube&…

作者头像 李华
网站建设 2026/10/18 0:12:27

单臂路由实验详解:VLAN间通信的配置原理与排错实战

单臂路由这个实验&#xff0c;算是VLAN间通信里最经典也最容易让人绕晕的一个了。很多人在模拟器里照着教程敲命令&#xff0c;明明每一步都做对了&#xff0c;两个PC就是ping不通。我刚开始学的时候也卡在这上面好几天&#xff0c;后来发现坑几乎都出在交换机接口的角色划分和…

作者头像 李华
网站建设 2026/10/22 23:18:50

基于Java Web的人才招聘系统设计与实现文档撰写指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 23:34:55

分治-快速排序

1.颜色划分 75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/sort-colors/description/ 分治就是分而治之&#xff0c;把一个非常大的问题&#xff0c;转化为很多相似的子问题。子问题基础上继续划分&#xff0c;直到划分到这个子问题可…

作者头像 李华