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

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

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

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

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

鸿蒙使用的ArkUI框架进行渲染,配套的ArkTS是声明式 编程,只需要关心数据的变化,数据变UI就相应的需要去更新。和传统的命令式 编程相比,省去了寻找对应UI组件,去填充改变刷新,再让UI进行刷新的过程。

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

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

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

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

代码举例如下:

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

dart 复制代码
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;
  }
}

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

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

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

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

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


代码举例如下:

首先要给需要刷新的多层对象定义类,添加@ObservedV2修饰。这个和V1区别不大。之后是使用@Trace直接修饰在需要监听的属性之前,代表该属性需要观察。此时我们就需要关心这个属性,在对象多少层了。

dart 复制代码
@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++;
        })
    }
  }
}

@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的配套指导。

相关推荐
whysqwhw2 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw3 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw5 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw6 小时前
鸿蒙音频编码
harmonyos
whysqwhw6 小时前
鸿蒙音频解码
harmonyos
whysqwhw6 小时前
鸿蒙视频解码
harmonyos
whysqwhw6 小时前
鸿蒙视频编码
harmonyos
ajassi20006 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos
前端世界7 小时前
在鸿蒙应用中快速接入地图功能:从配置到实战案例全解析
华为·harmonyos
江拥羡橙9 小时前
【基础-判断】HarmonyOS提供了基础的应用加固安全能力,包括混淆、加密和代码签名能力
安全·华为·typescript·harmonyos