写点什么

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

作者:GeorgeGcs
  • 2025-03-24
    上海
  • 本文字数:3695 字

    阅读完需:约 12 分钟

【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 的处理。由此逻辑推断,如果我的数据对象有十层,每层都有数据需要更新的业务逻辑,(极限情况),代码冗余十分致命。


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@Componentstruct 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) }) }}@Componentstruct 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 直接修饰在需要监听的属性之前,代表该属性需要观察。此时我们就需要关心这个属性,在对象多少层了。


@ObservedV2class Father {  @Trace name: string = "Tom";}class Son extends Father {}@Entry@ComponentV2struct Index {  son: Son = new Son();
build() { Column() { // 当点击改变name时,Text组件会刷新 Text(`${this.son.name}`) .onClick(() => { this.son.name = "Jack"; }) } }}@ObservedV2class Manager { @Trace static count: number = 1;}@Entry@ComponentV2struct 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的配套指导。

发布于: 刚刚阅读数: 4
用户头像

GeorgeGcs

关注

路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入

历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。 目前任职鸿蒙应用架构师。 HarmonyOS官方认证创作先锋

评论

发布
暂无评论
【HarmonyOS Next】鸿蒙状态管理V2装饰器详解_鸿蒙状态管理_GeorgeGcs_InfoQ写作社区