鸿蒙应用示例:状态管理与UI刷新机制从@State到@ObservedV2的进阶

在构建HarmonyOS应用时,状态管理是一项至关重要的任务。良好的状态管理不仅能让应用更加健壮,还能极大地提升用户体验。本文将探讨三种不同层次的状态管理策略,并分析它们对UI刷新机制的影响。
第一段代码:基础状态管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | export class ChildBean { name: string = "" //item名称 isSelect: boolean = false //是否选中 constructor(name: string) { this .name = name } } export class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = [] } @Entry @Component struct Index { @State fatherArr: FatherBean[] = [] aboutToAppear(): void { //向数组添加元素 let mFatherBean1: FatherBean = new FatherBean() mFatherBean1.name = '男生' mFatherBean1.childArr.push( new ChildBean( '杰克' )) mFatherBean1.childArr.push( new ChildBean( '李雷' )) mFatherBean1.childArr.push( new ChildBean( '小草' )) let mFatherBean2: FatherBean = new FatherBean() mFatherBean2.name = '女生' mFatherBean2.childArr.push( new ChildBean( '露丝' )) mFatherBean2.childArr.push( new ChildBean( '韩梅梅' )) mFatherBean2.childArr.push( new ChildBean( '小花' )) this .fatherArr.push(mFatherBean1) this .fatherArr.push(mFatherBean2) } build() { Column({ space: 10 }) { Text( '请选择班级大扫除名单' ) ForEach( this .fatherArr, (item: FatherBean, index: number) => { Text(`${item.name}组`) ForEach(item.childArr, (item_2: ChildBean, index_2: number) => { Row() { Button(`${item_2.name}`).onClick(() => { item_2.isSelect = !item_2.isSelect //修改数据 //替换指定索引元素,让数组元素地址变更,从而触发重绘 this .fatherArr.splice(index, 1, this .fatherArr[index]); //或者序列化后再反序列化。 //this.fatherArr[index]= JSON.parse(JSON.stringify(item)) }) Text(`${item_2.isSelect ? '参加' : '不参加' }`) } }) }) } .width( '100%' ) } } |
在第一段代码中,我们看到使用了@State修饰符来管理状态。这种方式适合处理较为简单的情况,如单个变量或者简单的数组结构。但是,当涉及到复杂的嵌套数据结构时,@State的局限性就显现出来了。
问题描述:
当修改嵌套数组中的某个元素时,由于@State仅能检测到顶层对象的变化,底层数据的变化不会自动触发UI的更新。因此,在这段代码中,虽然isSelect字段被修改了,但是如果没有采取额外措施(如替换数组元素),UI不会自动刷新。
解决方案:
通过替换数组中的元素,强制让数组的引用地址发生变化,从而触发UI的重新渲染。这虽然是一个可行的解决方案,但增加了代码的复杂性,并不是最佳实践。
第二段代码:@Observed与@ObjectLink的结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | @Observed export class ChildBean { name: string = "" //item名称 isSelect: boolean = false //是否选中 constructor(name: string) { this .name = name } } @Observed export class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = [] } @Entry @Component struct Index { @State fatherArr: FatherBean[] = [] aboutToAppear(): void { //向数组添加元素 let mFatherBean1: FatherBean = new FatherBean() mFatherBean1.name = '男生' mFatherBean1.childArr.push( new ChildBean( '杰克' )) mFatherBean1.childArr.push( new ChildBean( '李雷' )) mFatherBean1.childArr.push( new ChildBean( '小草' )) let mFatherBean2: FatherBean = new FatherBean() mFatherBean2.name = '女生' mFatherBean2.childArr.push( new ChildBean( '露丝' )) mFatherBean2.childArr.push( new ChildBean( '韩梅梅' )) mFatherBean2.childArr.push( new ChildBean( '小花' )) this .fatherArr.push(mFatherBean1) this .fatherArr.push(mFatherBean2) } build() { Column({ space: 10 }) { Text( '请选择班级大扫除名单' ) ForEach( this .fatherArr, (item: FatherBean, index: number) => { Text(`${item.name}组`) ForEach(item.childArr, (item_2: ChildBean, index_2: number) => { Item({ item_2: item_2 }) }) }) } .width( '100%' ) } } @Component struct Item { @ObjectLink item_2: ChildBean build() { Row() { Button(`${ this .item_2.name}`).onClick(() => { this .item_2.isSelect = ! this .item_2.isSelect //修改数据 }) Text(`${ this .item_2.isSelect ? '参加' : '不参加' }`) } } } |
在第二段代码中,我们看到使用了@Observed修饰符来标记类,并通过@ObjectLink在自定义组件中引用这些对象。这种方法允许我们更细粒度地控制UI的更新逻辑。
问题描述:
虽然使用@Observed可以标记整个类,使得其内部的状态变化能够被追踪,但对于深层嵌套的属性,依然存在一定的局限性。此外,使用@ObjectLink需要额外定义组件,增加了代码的复杂性。
解决方案:
通过定义自定义组件并在其中使用@ObjectLink,可以更好地管理复杂的状态。这种方式虽然增加了编写代码的工作量,但同时也提供了更高的灵活性和更好的状态隔离性。
第三段代码:@ObservedV2与@Trace的深度观测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | @ObservedV2 export class ChildBean { name: string = "" //item名称 @Trace isSelect: boolean = false //是否选中 constructor(name: string) { this .name = name } } export class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = [] } @Entry @Component struct Index { @State fatherArr: FatherBean[] = [] aboutToAppear(): void { //向数组添加元素 let mFatherBean1: FatherBean = new FatherBean() mFatherBean1.name = '男生' mFatherBean1.childArr.push( new ChildBean( '杰克' )) mFatherBean1.childArr.push( new ChildBean( '李雷' )) mFatherBean1.childArr.push( new ChildBean( '小草' )) let mFatherBean2: FatherBean = new FatherBean() mFatherBean2.name = '女生' mFatherBean2.childArr.push( new ChildBean( '露丝' )) mFatherBean2.childArr.push( new ChildBean( '韩梅梅' )) mFatherBean2.childArr.push( new ChildBean( '小花' )) this .fatherArr.push(mFatherBean1) this .fatherArr.push(mFatherBean2) } build() { Column({ space: 10 }) { Text( '请选择班级大扫除名单' ) ForEach( this .fatherArr, (item: FatherBean, index: number) => { Text(`${item.name}组`) ForEach(item.childArr, (item_2: ChildBean, index_2: number) => { Row() { Button(`${item_2.name}`).onClick(() => { item_2.isSelect = !item_2.isSelect //修改数据 }) Text(`${item_2.isSelect ? '参加' : '不参加' }`) } }) }) } .width( '100%' ) } } |
在第三段代码中,我们看到了使用@ObservedV2和@Trace装饰器的组合来实现深度观测。这种方法允许开发者对复杂对象的内部状态进行细致的控制,并确保任何细微的变化都能被捕捉到。
问题描述:
对于复杂的嵌套对象,仅仅依靠@State或简单的@Observed是不够的。我们需要一种机制来确保即使是最深层的属性变化也能被UI正确响应。
解决方案:
使用@ObservedV2来标记整个类,并结合@Trace来标记需要被追踪的具体属性。这种方法可以实现对对象内部状态的深度追踪,确保任何属性的变化都能够触发UI的重新渲染。
总结与建议
通过对比三种不同的状态管理策略,我们可以得出以下结论:
1. 基本状态管理(使用@State):适用于简单的状态,但对于复杂数据结构的支持不足。
2. 中等状态管理(使用@Observed与@ObjectLink):适合处理较为复杂的状态,但增加了代码的复杂性。
3. 高级状态管理(使用@ObservedV2与@Trace):最适合处理复杂的嵌套数据结构,能够提供深度观测能力,确保UI准确响应状态变化。
在实际应用开发中,开发者应当根据自己的具体需求来选择最合适的状态管理方案。对于较为简单的应用,使用@State可能已经足够;而对于复杂应用,尤其是涉及到多层次嵌套的状态管理,则推荐使用@ObservedV2和@Trace的组合。
通过合理的状态管理,我们不仅能简化代码结构,还能显著提升用户体验,让我们的HarmonyOS应用更加稳定可靠。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了