在构建 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
@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 的结合
@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 的深度观测
@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 应用更加稳定可靠。
评论