HarmonyOS鸿蒙Next应用V1状态装饰器使用List组件多层对象嵌套ForEach渲染更新的处理

HarmonyOS鸿蒙Next应用V1状态装饰器使用List组件多层对象嵌套ForEach渲染更新的处理

鸿蒙应用V1状态装饰器使用List组件多层对象嵌套ForEach渲染更新的处理

3 回复

一、结论

在鸿蒙中UI更新渲染的机制,与传统的Android IOS应用开发相比。开发会简单许多,开发效率提升显著。

一般传统应用开发的流程处理分为三步:1.画UI,2.获得or创建,处理数据,3.增删改数据,找到对应控件,更新到UI上。

而鸿蒙应用开发,大大提供效率其中一点,就是减少了第三步。我们只需要关心数据源的变化,数据自动会更新到对应的控件上。

这种处理机制,其实符合应用开发的时代潮流,目前Android和IOS最新框架机制都有相应类似的处理。例如swiftUI,Compose。并且Vue,Flutter整个刷新机制就是如此。

众所周知,在HarmonyOs的list组件渲染中,如果使用的是V1状态装饰器。如果数据源列表对象是多个对象嵌套的处理,那最底层对象的属性更新时,UI界面是不会渲染的。因为检测不到,目前只能检测到第一层对象。

二、代码实现和详细解释

针对多层对象嵌套,底层对象属性修改后,UI不渲染的问题,有个比较简单又方便的处理方式,思路仅供参考。

即:深拷贝修改的item对象。

这样整个对象相当于都变化了,就符合第一层对象检测的机制,可以被系统捕获到刷新。

import { util } from '@kit.ArkTS';

/**
 * 二级数据结构
 */
class ChildInfo {
  index: number;

  constructor(index: number) {
    this.index = index;
  }
}

/**
 * 一级数据结构
 */
class ItemInfo {
  key: string = util.generateRandomUUID(true);
  name: string;
  icon: Resource;
  childInfo: ChildInfo;
  select: boolean;

  constructor(name: string, icon: Resource, index: number) {
    this.name = name;
    this.icon = icon;
    this.childInfo = new ChildInfo(index);
    this.select = false;
  }

  /**
   * 重新创建对象,深拷贝处理
   * @param itemInfo
   * @param index
   * @returns
   */
  static deepCopy(itemInfo: ItemInfo, index: number){
    let info: ItemInfo = new ItemInfo(itemInfo.name, itemInfo.icon, index);
    info.select = itemInfo.select;
    info.key = itemInfo.key;
    info.childInfo = itemInfo.childInfo;
    return info;
  }
}

/**
 *
 */
@Entry
@Component
struct ForeachPage {

  private TAG: string = "ForeachPage";

  @State mListData: Array<ItemInfo> = [];

  aboutToAppear(): void {
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 1));
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 2));
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 3));
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 4));
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 5));
    this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 6));
  }

  @Builder ItemView(item: ItemInfo, index: number){
    Row() {
      Image(item.icon)
        .width(px2vp(120))
        .height(px2vp(120))

      Text(item.name + "(" + item.childInfo.index + ")").fontSize(20)

      Blank()

      if(this.isLog(item, index)){
        if(item.select){
          Image($r("app.media.icon_check"))
            .size({
              width: px2vp(72),
              height: px2vp(72)
            })
        }
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.Start)
    .onClick(()=>{
      item.select = !item.select;
      if(item.select){
        item.childInfo.index = 666;
      }else{
        item.childInfo.index = index;
      }
      this.mListData[index] = ItemInfo.deepCopy(item, item.childInfo.index);
      console.log(this.TAG, " ItemView onClick: " + index + " item.select: " + item.select);
    })
  }

  private isLog(item: ItemInfo, index: number){
    console.log(this.TAG, " ItemView isLog index: " + index + " item.select: " + item.select);
    return true;
  }

  build() {
    List() {
      ForEach(this.mListData, (item: ItemInfo, index: number) => {
        ListItem() {
          this.ItemView(item, index)
        }
      }, (item: ItemInfo) => JSON.stringify(item))
      //
    }
    .width("100%")
    .height("100%")
    .padding({ left: px2vp(60), right: px2vp(60) })
  }
}

更多关于HarmonyOS鸿蒙Next应用V1状态装饰器使用List组件多层对象嵌套ForEach渲染更新的处理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,使用@State装饰器管理多层嵌套对象时,ForEach渲染更新需注意数据结构的响应式处理。若直接修改嵌套对象的属性,UI可能不会自动刷新。建议使用@Observed装饰器标记嵌套对象的类,并在其属性上使用@ObjectLink@Prop装饰器来建立响应式关联。这样,当嵌套对象的属性发生变化时,ForEach会触发组件更新。确保数据源为数组且提供唯一键值生成函数。

在HarmonyOS Next中,使用V1状态装饰器(如@State)结合List组件进行多层对象嵌套的ForEach渲染更新时,关键在于确保状态变化的可观测性,以驱动UI正确刷新。

核心原则@State装饰的变量本身是响应式的,但其嵌套对象内部属性的直接修改可能无法被UI层观测到,从而导致ForEach不更新。

典型场景与解决方案

假设数据结构为:

@State class Group {
  name: string = '';
  items: Item[] = []; // Item也是一个类
}

@State class Item {
  id: number = 0;
  text: string = '';
}

@State groups: Group[] = [];

List中嵌套使用ForEach渲染groups和每个group.items

1. 更新整个数组(替换引用)

最直接的方式是替换整个数组引用,这会触发最彻底的UI刷新。

// 添加一个新组
this.groups = [...this.groups, new Group(...)];
// 或者修改后重新赋值
this.groups[index].items.push(new Item(...));
this.groups = [...this.groups]; // 关键:创建新数组以触发更新

2. 更新嵌套对象的属性

若只修改深层属性(如item.text),需确保修改操作能被观测:

  • 对于@State装饰的类:直接修改其属性(如this.groups[index].items[i].text = 'new'通常可以触发更新,因为V1装饰器对类实例提供了深度的响应式支持。但为了绝对可靠,尤其是在多层嵌套下,建议在修改后通知父层状态变化:
    this.groups[index].items[i].text = 'new';
    this.groups = [...this.groups]; // 通过父数组引用的变化触发更新
    
  • 使用@Observed@ObjectLink:对于更复杂的嵌套对象,这是V1中推荐的深度观测方案。
    1. @Observed装饰嵌套的类(如GroupItem)。
    2. 在父组件中,使用@ObjectLink装饰接收嵌套对象的变量(而非@State)。 此组合允许UI直接观测到嵌套对象内部属性的变化,无需替换上层引用。但请注意,@ObjectLink必须用于对象属性,且通常与@Observed配合使用。

3. 使用唯一标识键

ForEach中,为每个项目提供稳定且唯一的key(如item.id)至关重要,这能帮助ArkUI框架准确识别项目的增、删、改,从而高效更新DOM。

总结建议

  • 优先考虑替换数组引用:对于多层嵌套,在修改深层数据后,直接替换最顶层数组的引用(this.groups = [...this.groups])是最简单、最可靠的更新触发方式。
  • 复杂场景使用@Observed/@ObjectLink:如果嵌套层次很深,且更新频繁,考虑使用@Observed@ObjectLink实现精细化的响应式更新。
  • 避免在ForEach内直接修改状态:状态修改应集中在组件的方法中,保证数据流的清晰。

通过上述方法,可以确保多层嵌套对象在ListForEach中渲染时,状态更新能正确反映到UI界面。

回到顶部