写点什么

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

作者:zhongcx
  • 2024-10-12
    广东
  • 本文字数:3587 字

    阅读完需:约 12 分钟

在构建 HarmonyOS 应用时,状态管理是一项至关重要的任务。良好的状态管理不仅能让应用更加健壮,还能极大地提升用户体验。本文将探讨三种不同层次的状态管理策略,并分析它们对 UI 刷新机制的影响。

第一段代码:基础状态管理

export class ChildBean {  name: string = "" //item名称  isSelect: boolean = false //是否选中
constructor(name: string) { this.name = name }}
export class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = []}
@Entry@Componentstruct 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 的结合

@Observedexport class ChildBean {  name: string = "" //item名称  isSelect: boolean = false //是否选中
constructor(name: string) { this.name = name }}
@Observedexport class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = []}
@Entry@Componentstruct 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%') }}
@Componentstruct 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 的深度观测

@ObservedV2export class ChildBean {  name: string = "" //item名称  @Trace isSelect: boolean = false //是否选中
constructor(name: string) { this.name = name }}
export class FatherBean { name: string = "" //组名称 childArr: ChildBean[] = []}
@Entry@Componentstruct 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 应用更加稳定可靠。

用户头像

zhongcx

关注

还未添加个人签名 2024-09-27 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙应用示例:状态管理与UI刷新机制从@State到@ObservedV2的进阶_zhongcx_InfoQ写作社区