【HarmonyOS Next】鸿蒙状态管理V2装饰器详解 原创

George_wu_
发布于 2025-3-24 23:31
浏览
0收藏

【HarmonyOS Next】鸿蒙状态管理V2装饰器详解

一、为什么需要V2状态管理装饰器?

首先我们需要了解什么是状态管理?在鸿蒙应用开发中,状态管理指的是,管理数据变化去刷新UI的整个过程。

举个例子,比如在界面中标题文本的动态刷新,从A刷新成B,这个文本的刷新过程,其实就是个状态的变化过程。整个过程的处理可以称之为状态管理。

鸿蒙使用的ArkUI框架进行渲染,配套的ArkTS是声明式编程,只需要关心数据的变化,数据变UI就相应的需要去更新。和传统的命令式编程相比,省去了寻找对应UI组件,去填充改变刷新,再让UI进行刷新的过程。
【HarmonyOS Next】鸿蒙状态管理V2装饰器详解-鸿蒙开发者社区

为了实现上述的UI动态渲染效果。鸿蒙提供了状态装饰器的概念工具,来实现数据到UI的便捷更新同步。

【HarmonyOS Next】鸿蒙状态管理V2装饰器详解-鸿蒙开发者社区

V1状态装饰器于此产生:围绕着@State这个数据监听开关,配套的装饰器来一起实现刷新行为,因为UI界面分为组件,界面。需要维护子母关系。所以就用到了@Prop 单数据流动,@Link 双数据流动,@Provide/@Consume 跨层级传递数据,@Observed和@ObjectLink 监听实现多层级嵌套对象的更新。

自从api7开始,一直到api10。V1的实际使用中,开发人员发现@Observed和@ObjectLink 监听实现多层级嵌套对象的更新的方案,太过于臃肿。

当需要监听处理更新的多层级对象是七八层,就需要配套创建七八层的ObjectLink,代码太过于冗余。

代码举例如下:
需要进行嵌套数据更新的层级,要抽离ItemView,在其中进行ObjectLink的处理。由此逻辑推断,如果我的数据对象有十层,每层都有数据需要更新的业务逻辑,(极限情况),代码冗余十分致命。

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

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

}

/**
 * 二级数据结构
 */
@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 // 每一级数据结构都需要用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 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)) 
    }
    .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 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;
  }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.

正因为以上的缺陷,V2状态管理装饰器由此诞生。但是目前开发进程还有堵塞点,官方建议慎重使用V2。

二、V2状态管理装饰器怎么用?

综上所述,我们知道解决多层嵌套对象刷新的痛点,是V2的主要任务。

其中@ObservedV2和@Trace,代替了@Observed和@ObjectLink。

相对于V1的监听原理,使用代理模式的监听数据变化。V2更加彻底且解耦,直接在数据本身进行观察监听。

【HarmonyOS Next】鸿蒙状态管理V2装饰器详解-鸿蒙开发者社区
代码举例如下:
首先要给需要刷新的多层对象定义类,添加@ObservedV2修饰。这个和V1区别不大。之后是使用@Trace直接修饰在需要监听的属性之前,代表该属性需要观察。此时我们就需要关心这个属性,在对象多少层了。

@ObservedV2
class Father {
  @Trace name: string = "Tom";
}
class Son extends Father {
}
@Entry
@ComponentV2
struct Index {
  son: Son = new Son();

  build() {
    Column() {
      // 当点击改变name时,Text组件会刷新
      Text(`${this.son.name}`)
        .onClick(() => {
          this.son.name = "Jack";
        })
    }
  }
}
@ObservedV2
class M
anager {
  @Trace static count: number = 1;
}
@Entry
@ComponentV2
struct Index {
  build() {
    Column() {
      // 当点击改变count时,Text组件会刷新
      Text(`${Manager.count}`)
        .onClick(() => {
          Manager.count++;
        })
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

@ComponentV2装饰器:自定义组件
@Local 组件内部状态
@Local可理解为v1版本的@State,当被@Local装饰的变量变化时,会刷新使用该变量的组件。

@Param 组件外部输入
@Param可理解为v1版本的@Prop,但不同的是,如果单一使用@Param,则不可修改子组件当前的数据用于改变UI展示,修改时会报错,若想修改,则需要再增加一个@Once 装饰器。

@Once 初始化同步一次
增加@once 装饰器后可修改 msg 的值,会触发当前组件UI更新,但数据不会同步至父组件。需要注意:仅初始化时同步数据源一次,之后不再继续同步变化的场景。比如父组件 @Local装饰的变量存在初始值,传递给@Once装饰的变量后,再次修改@Local变量的值,子组件的值不会发生变化。

@Require
@Param默认需要设置初始值,若不想设置初始值,需要增加 @Require 装饰器。

@Event 规范组件输出
实现子组件向父组件要求更新@Param装饰变量的能力(数据双向绑定),父组件传递至子组件的数据,若子组件想修改数据并同步至父组件,则需要使用该装饰器实现。@Event装饰器只可修饰回调方法,装饰string或number等类型不报错,但也无法发挥其装饰器功能。

使用@Event和@Param实现数据双向绑定。

三、V2状态管理装饰器的优点和不足

V2的优点

  1. 具备深度观察、属性级更新。
  2. 组件中明确状态变量的输入与输出,利于组件化
  3. 状态变量能独立于UI存在,同一个数据被多个视图代理时,在其中一个视图的更改会通知其他视图更新

V2的不足

  1. 复杂对象时V1的@State能够观察复杂对象的第一层属性变化,但V2的@Local无法观察对象内部变化。为了解决这个问题,需要在类上添加@ObservedV2,并在需要观察的属性上添加@Trace。这样,框架就能追踪对象内部的属性变化。相对于V1稍微麻烦些。
  2. animateTo暂不支持直接在状态管理V2中使用。

点击跳转官方V1迁移V2的配套指导。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐