HarmonyOS鸿蒙Next应用V1状态装饰器多层嵌套对象如何通过@ObjectLink和@Observed实现渲染更新处理

HarmonyOS鸿蒙Next应用V1状态装饰器多层嵌套对象如何通过@ObjectLink@Observed实现渲染更新处理

鸿蒙应用V1状态装饰器多层嵌套对象如何通过@ObjectLink@Observed实现渲染更新处理

3 回复

一、结论

若结构对象层级嵌套很多,属性量级大。使用深拷贝的方法就显得得不偿失了。

那应该怎么处理呢?其实还可以拆分大的数据源对象,拆薄。新增@State修饰的状态变量控制刷新。

不过这种方案,对于历史业务逻辑开发是不友好的,因为我们的历史业务数据结构是固定,不方便拆分。

在以上方案都不能解决的情况下,官网推荐了一种方式,可以无感知的实现,多层嵌套的对象属性变化,就刷新渲染对应的列表UI。

该方案唯一的缺点就是需要对于item view需进行组件Component申明,需要将list包裹的item UI进行拆分剥离。如果套的层数过多,需要写很多套模板代码。该问题已经在V2解决。如果是可以更换V2,建议直接更换为V2。如果不行,请查看以下内容。

二、代码实现和详细解释

实现该方案效果需要以下详细步骤: 1.对嵌套的数据结构类,进行@Observed修饰

2.item UI拆分剥离为组件Component

3.数据源item数据在Component组件中使用@ObjectLink监听变化,以便于通知给父组件的@State修饰的数据源列表数据。

以上步骤完成后,调用item数据对象直接修改任意层级属性值,列表就会同步更新渲染。

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

/**
 * 三级数据结构
 */
[@Observed](/user/Observed) // 每一级数据结构都需要用Observed修饰
class GrandsonInfo {
  content: string = "";

}

/**
 * 二级数据结构
 */
[@Observed](/user/Observed) // 每一级数据结构都需要用Observed修饰
class ChildInfo {
  index: number;
  grandsonInfo: GrandsonInfo;

  constructor(index: number, content: string) {
    this.index = index;
    this.grandsonInfo = new GrandsonInfo();
    this.grandsonInfo.content = content;
  }
}

/**
 * 一级数据结构
 */
[@Observed](/user/Observed) // 每一级数据结构都需要用Observed修饰
class ItemInfo {
  key: string = util.generateRandomUUID(true);
  name: string;
  icon: Resource;
  childInfo: ChildInfo;
  select: boolean;

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

/**
 * 多层嵌套刷新渲染
 */
@Entry
@Component
struct ObservedPage {
  private TAG: string = "ObservedPage";

  [@State](/user/State) mListData: Array<ItemInfo> = [];

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

  build() {
    List() {
      ForEach(this.mListData, (item: ItemInfo, index: number) => {
        ListItem() {
          // ListItem包裹的ItemView需要抽离成Component组件的形态,参数通过属性赋值传递,即:大括号包裹中,属性值key value形式赋值
          ItemView({
            item: item,
            index: index
          })
        }
      }, (item: ItemInfo) => JSON.stringify(item)) // , (item: ItemInfo) => JSON.stringify(item)
      // keyGenerator: ArkUI框架会对重复的键值发出警告。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作. [渲染异常]
      // 除非必要,否则不推荐将第三个参数KeyGenerator函数处于缺省状态,应尽量避免最终键值生成规则中包含index。[渲染性能降低]
    }
    .width("100%")
    .height("100%")
    .padding({ left: px2vp(60), right: px2vp(60) })
  }
}


@Component
struct ItemView {

  private TAG: string = "ItemView";

  @Prop index: number = 0;
  // 列表数据的单个item对象数据,需要使用ObjectLink修饰监听,用于将数据变化传递给外部父组件的mListData
  [@ObjectLink](/user/ObjectLink) item: ItemInfo

  build() {
    Row() {
      Image(this.item.icon)
        .width(px2vp(200))
        .height(px2vp(200))

      Text(this.item.name + "(" + this.item.childInfo.index + ")" + " [ " + this.item.childInfo.grandsonInfo.content + " ] ")
        .fontSize(px2fp(52))

      Blank()

      if(this.isLog(this.item, this.index)){
        if(this.item.select){
          Image($r("app.media.icon_check"))
            .size({
              width: px2vp(72),
              height: px2vp(72)
            })
        }
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.Start)
    .onClick(()=>{
      this.item.select = !this.item.select;
      if(this.item.select){
        // 使用很方便,只需要直接改变item数据的任意层级属性值,变化就会同步刷新
        this.item.childInfo.index = 666;
        this.item.childInfo.grandsonInfo.content = "鹅厂23333"
      }else{
        this.item.childInfo.index = this.index;
        this.item.childInfo.grandsonInfo.content = "鹅厂" + this.index;
      }
      console.log(this.TAG, " ItemView onClick: " + this.index + " item.select: " + this.item.select);
    })
  }

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

更多关于HarmonyOS鸿蒙Next应用V1状态装饰器多层嵌套对象如何通过@ObjectLink和@Observed实现渲染更新处理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next应用中,处理多层嵌套对象的渲染更新需使用@Observed@ObjectLink装饰器。首先,用@Observed装饰嵌套对象的类。在父组件中,使用@ObjectLink装饰嵌套对象属性,该属性引用@Observed类的实例。当嵌套对象的属性变化时,@ObjectLink会触发UI更新。确保@ObjectLink装饰的属性是@Observed类的实例,而非其数组或集合。

在HarmonyOS Next中,处理多层嵌套对象的渲染更新,关键在于正确使用@Observed@ObjectLink装饰器来建立可观察的数据模型与UI组件之间的响应式连接。以下是核心实现方案:

1. 数据模型定义

首先,使用@Observed装饰嵌套对象类,使其具备被观察的能力。这会让该类的所有属性变更都能被UI感知。

// 定义深层嵌套的类,并用@Observed装饰
@Observed
class InnerData {
  value: string;

  constructor(value: string) {
    this.value = value;
  }
}

@Observed
class MiddleData {
  inner: InnerData;
  info: string;

  constructor(inner: InnerData, info: string) {
    this.inner = inner;
    this.info = info;
  }
}

@Observed
class MyObject {
  middle: MiddleData;
  count: number;

  constructor(middle: MiddleData, count: number) {
    this.middle = middle;
    this.count = count;
  }
}

2. UI组件与数据绑定

在UI组件中,使用@ObjectLink装饰器来接收和监听嵌套对象的特定层级实例。@ObjectLink会与数据源中对应层级的@Observed对象建立单向同步,确保该层级的属性变更能触发UI更新。

// 子组件,负责渲染MiddleData层
@Component
struct MiddleComponent {
  @ObjectLink middleData: MiddleData // 绑定到MiddleData实例

  build() {
    Column() {
      Text(`Middle Info: ${this.middleData.info}`)
        .fontSize(18)
      // 进一步传递内部对象给更深的子组件
      InnerComponent({ innerData: this.middleData.inner })
    }
  }
}

// 孙子组件,负责渲染InnerData层
@Component
struct InnerComponent {
  @ObjectLink innerData: InnerData // 绑定到InnerData实例

  build() {
    Text(`Inner Value: ${this.innerData.value}`)
      .fontSize(16)
      .onClick(() => {
        // 点击修改,触发更新
        this.innerData.value += '!'
      })
  }
}

// 父组件,持有顶级数据
@Entry
@Component
struct ParentComponent {
  @State rootData: MyObject = new MyObject(
    new MiddleData(new InnerData('Hello'), 'Mid Info'),
    0
  )

  build() {
    Column() {
      // 传递中间层对象给子组件
      MiddleComponent({ middleData: this.rootData.middle })
      Text(`Root Count: ${this.rootData.count}`)
        .onClick(() => {
          this.rootData.count += 1
        })
    }
  }
}

关键机制说明

  • @Observed:装饰类,使该类实例的属性变化可被观测。需在每一层嵌套的类上使用。
  • @ObjectLink:装饰组件内的变量,与父组件提供的@Observed对象属性建立引用关联。当该@Observed对象的属性变化时,会触发当前组件的更新。
  • 更新传递:当innerData.value被修改时,由于InnerData类被@Observed装饰,且InnerComponent通过@ObjectLink引用了该实例,因此InnerComponent会重新渲染。这个更新不会向上冒泡到MiddleComponentParentComponent,实现了更新的局部化。
  • @Link的区别@ObjectLink专门用于接收@Observed对象的子属性(对象引用),并与之同步;而@Link用于同步基本类型或简单对象。

注意事项

  • 确保嵌套对象的每一层类都使用@Observed装饰。
  • @ObjectLink变量必须初始化为undefined或在构造参数中赋值,不能设置默认值。
  • 通过@ObjectLink绑定的对象,应直接修改其属性(如this.innerData.value = 'new'),而非重新赋值(this.innerData = new InnerData()),重新赋值会破坏响应式连接。

此方案通过@Observed@ObjectLink的配合,实现了多层嵌套对象数据变化的精准、高效UI更新。

回到顶部