在构建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应用更加稳定可靠。


zhongcx
1 声望3 粉丝