ArkTS--状态管理

news2024/12/23 4:15:16

一、概述

        在声明式UI编程范式中,UI是应用程序状态的函数,应用程序状态的修改会更新相应的UI界面。ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,更新数据的时候直接更新视图。如下图所示:

        

        ArkUI提供了一系列装饰器实现ViewModel的能力,如@Prop、@Link、@Provide、LocalStorage等。当自定义组件内变量被装饰器装饰时变为状态变量,状态变量的改变会引起UI的渲染刷新。

        在ArkUI的开发过程中,如果没有选择合适的装饰器或合理的控制状态更新范围,可能会导致以下问题:

        1. 状态和UI的不一致,如同一状态的界面元素展示的UI不同,或UI界面展示的不是最新的状态。

        2. 非必要的UI视图刷新,如只修改局部组件状态时导致组件所在页面的整体刷新。

        当用户与界面产生交互行为时,状态的修改是通过事件驱动处理的。事件的处理可以在应用的任何地方,如果没有进行适当的逻辑处理管理也会导致代码冗余和不利于维护。

二、合理选择装饰器

        1、避免不必要的状态变量的使用

        a、删除冗余的状态变量标记

        状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化。

@Observed
class Translate {
  translateX: number = 20;
}

@Entry
@Component
struct UnnecessaryState1 {
  @State translateObj: Translate = new Translate(); // 同时存在读写操作,并关联了Button组件,推荐使用状态变量
  buttonMsg = 'I am button'; // 仅读取变量buttonMsg的值,没有任何写的操作,直接使用一般变量即可

  build() {
    Column() {
      Button(this.buttonMsg)
        .onClick(() => {
          animateTo({
            duration: 50
          }, () => {
            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150; // 点击时给变量translateObj重新赋值
          })
        })
    }
    .translate({
      x: this.translateObj.translateX // 读取translateObj中的值
    })
  }
}

        b、建议使用临时变量替换状态变量        

        状态变量发生变化时,ArkUI会查询依赖该状态变量的组件并执行依赖该状态变量的组件的更新方法,完成组件渲染的行为。通过使用临时变量的计算代替直接操作状态变量,可以使ArkUI仅在最后一次状态变量变更时查询并渲染组件,减少不必要的行为,从而提高应用性能。

@Entry
@Component
struct UnnecessaryState2 {
  @State message: string = '';

  appendMsg(newMsg: string) {
    let message = this.message;
    message += newMsg;
    message += ';';
    message += '<br/>';
    this.message = message;
  }

  build() {
    Column() {
      Button('点击打印日志')
        .onClick(() => {
          this.appendMsg('操作临时变量');
        })
        .width('90%')
        .backgroundColor(Color.Blue)
        .fontColor(Color.White)
        .margin({ top: 10 })
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Center)
    .margin({ top: 15 })
  }
}

        2、最小化状态共享范围

        在没有强烈的业务需求下,尽可能按照状态需要共享的最小范围选择合适的装饰器。应用开发过程中,按照组件颗粒度,状态一般分为组件内独享的状态和组件间需要共享的状态。

        a、组件内独享的状态

        组件内独享的状态的生命周期和组件同步,状态的定义和更新都在组件内,组件销毁,状态也随即消失。常见于界面UI元素数据,比如当前按钮是否可用、文字是否高亮等。组件内独享的状态使用@State装饰器,被@State装饰器修饰后状态的修改只会触发当前组件实例的重新渲染。

        b、组件间需要共享的状态

        组件间需要共享的状态,按照共享范围从小到大依次有三种场景:父子组件间共享状态,不同子树上组件间共享状态和不同组件树间共享状态。 

         

        对于上述三种场景,ArkUI提供了@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink、@Provide+@Consume、AppStorage、LocalStorage六种装饰器组合以解决不同范围内的组件间状态共享。按照共享范围能力从小到大,各装饰器组合的共享范围能力和生命周期如下:

  1. @State+@Prop、@State+@Link、@State+@Observed+@ObjectLink:三者的共享范围为从@State所在的组件开始,到@Prop/@Link/ObjectLink所在组件的整条路径,路径上所有的中间组件通过@Prop/@Link/@ObjectLink都可以共享同一个状态。@State修饰的状态和其所属的自定义组件共享生命周期,在组件内定义时创建,组件销毁时被回收。@Link装饰的变量和其所属的自定义组件共享生命周期。@ObjectLink装饰的变量和其所属的自定义组件共享生命周期。
  2. @Provide+@Consume:状态共享范围是以@Provide所在组件为祖先节点的整棵子树,子树上的任意后代组件通过@Consume都可以共享同一个状态。@Provide修饰的变量与其所属的组件绑定,在组件内定义时被创建,在组件销毁时被回收。
  3. LocalStorage:共享范围为UIAbility内以页面为单位的不同组件树间的共享。存储在LocalStorage中的状态的生命周期与LocalStorage绑定。LocalStorage的声明周期由应用程序决定,当应用释放最后一个指向LocalStorage的引用时,LocalStorage被垃圾回收。
  4. AppStorage:共享范围是应用全局。AppStorage与应用的进程绑定,由UI框架在应用程序启动时创建,当应用进程终止,AppStorage被回收。存储在AppStorage中的状态的生命周期与LocalStorage绑定。

        按照软件开发原则,应优先选择共享范围能力小的装饰器方案,减少不同模块间的数据耦合,便于状态及时回收。建议选择装饰器的优先级为:@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink > @Provide+@Consume > LocalStorage > AppStorage。

         3、减少不必要的参数层层传递

        当按照上述优先级选择装饰器时,由于@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink三种方案的实现方式是逐级向下传递状态,当共享状态的组件间层级相差较大时,会出现状态层层传递的现象。对于状态传递过程中途经的全部组件,都需要增加入参接收该状态再将状态传递给子组件。对于没有使用该状态的中间组件而言,这是“额外的消耗”,不利于代码的维护和拓展。尤其是当业务体系庞大时,需求变更容易出现“牵一发而动全身”的问题。

        4、按照状态复杂度选择装饰器

        

        @State+@Prop组合方案:

  • @Prop装饰器支持接收Object、class、string、number、boolean、enum类型,以及这些类型的数组。
  • @Prop装饰的变量是对父组件传入状态值的深拷贝,当@Prop装饰器装饰的变量为复杂Object、class或其类型数组时,会增加状态创建时间以及占用大量内存。
  • @Prop装饰的变量和父组件是单向绑定的关系。当父组件数据源发生变化时,接收该数据源的@Prop所在组件的实例会重新渲染。 当该组件内被@Prop装饰的变量被修改时,父组件数据源不会变化,父组件实例也不会重新渲染。

        @State+@Link组合方案:

  • @Link装饰器支持接收Object、class、string、number、boolean、enum类型,以及这些类型的数组。
  • @Link装饰器修饰的变量是对父组件传入状态的引用的拷贝,两者指向同一个地址。当状态是简单数据类型或简单Object类型时,@Link和@Prop在状态创建时间和内存的占用方面区别不大。当状态为复杂的Object、class或其类型数组时,@Link相较@Prop能明显减少状态创建时间和内存的占用。
  • @Link装饰器的变量和父组件是双向绑定的关系。当父组件数据源发生变化时,接收该数据源的@Link所在组件的实例会重新渲染。 当该组件内被@Link装饰的变量被修改时,父组件数据源会同步修改,父组件实例也会重新渲染。

        @State+@Observed+@ObjectLink组合方案:

  • @ObjectLink只支持接收被@Observed装饰的class实例及继承Date或者Array的class实例。
  • @ObjectLink装饰的变量是只读的,不支持对状态重新赋值。
  • @ObjectLink必须配合@Observed使用,它的设计是为了解决对嵌套类对象属性变化的监听,如需要观察对象数组中单个数据项的属性值变化,或嵌套对象的对象类型属性的子属性变化。

         结合三个方案的特性,在选择时有如下建议:

  • 需要观察嵌套类对象的深层属性变化的场景,选择@State+@Observed+@ObjectLink。
  • 状态是复杂对象、类或其类型数组的场景,选择@State+@Link。
  • 状态是简单数据类型时,使用@State+@Link和@State+@Prop均可。在功能层面上,依据@Prop单向绑定的特性,@State+@Prop适合用于非实时修改的场景,如编辑电话薄联系人信息时,展示编辑界面的子组件信息的修改要求不实时同步回父组件,需要等到编辑完成后点击“确认”按钮时才会以事件驱动的方式修改父组件的状态。依据@Link双向绑定的特性,@State+@Link适合用于实时修改的场景,如组件嵌套时的滚动条同步。

三、精细化拆分复杂状态 

        对于AppStorage的使用,由于其作用范围最广,开发者为了方便开发容易将各种状态存入其中以达到共享的目的,这通常会造成大量的性能损失。

        这是因为,在ArkUI中状态的修改刷新是粗颗粒的。使用装饰器修饰对象类型状态时,ArkUI能监听到对象本身值的变化以及对象的属性值的变化。当对象属性值发生变化后,ArkUI会以整个对象颗粒度通知所有使用了该状态的组件重新渲染,而不是按属性颗粒度大小通知使用了该变化属性的组件重新渲染。

五、集中化状态修改逻辑

        在使用@Link装饰器时,开发者可以直接在@Link装饰器接收状态的组件内部修改状态。当多个子组件修改状态的逻辑基本相同时,建议将状态的修改集中到单个函数中,以提升逻辑的可复用性、代码的可维护性和可测试性。

六、使用监听和订阅精准控制组件刷新

        多个组件依赖对象中的不同属性时,直接关联该对象会出现改变任一属性所有组件都刷新的现象,可以通过将类中的属性拆分组合成新类的方式精准控制组件刷新。

        在多个组件依赖同一个数据源并根据数据源变化刷新组件的情况下,直接关联数据源会导致每次数据源改变都刷新所有组件。为精准控制组件刷新,可以采取以下策略。

        1、使用 @Watch 装饰器监听数据源

        在组件中使用@Watch装饰器监听数据源,当数据变化时执行业务逻辑,确保只有满足条件的组件进行刷新。

@Entry
@Component
struct UseWatchListener {
  @State currentIndex: number = 0; // 当前选中的列表项下标
  private listData: string[] = [];

  aboutToAppear(): void {
    for (let i = 0; i < 10; i++) {
      this.listData.push(`组件 ${i}`);
    }
  }

  build() {
    Row() {
      Column() {
        List() {
          ForEach(this.listData, (item: string, index: number) => {
            ListItem() {
              ListItemComponent({ item: item, index: index, currentIndex: this.currentIndex })
            }
          })
        }
        .height('100%')
        .width('100%')
        .alignListItem(ListItemAlign.Center)
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct ListItemComponent {
  @Prop item: string;
  @Prop index: number; // 列表项的下标
  @Link @Watch('onCurrentIndexUpdate') currentIndex: number;
  @State color: Color = Math.abs(this.index - this.currentIndex) <= 1 ? Color.Red : Color.Blue;

  isRender(): number {
    console.info(`ListItemComponent ${this.index} Text is rendered`);
    return 50;
  }

  onCurrentIndexUpdate() {
    // 根据当前列表项下标index与currentIndex的差值来动态修改color的值
    this.color = Math.abs(this.index - this.currentIndex) <= 1 ? Color.Red : Color.Blue;
  }

  build() {
    Column() {
      Text(this.item)
        .fontSize(this.isRender())
        .fontColor(this.color)
        .onClick(() => {
          this.currentIndex = this.index;
        })
    }
  }
}

        被依赖的数据源仅在父子或兄弟关系的组件中传递时,可以参考上述示例,使用@State/@Link/@Watch装饰器进行状态管理,实现组件的精准刷新。

        当组件关系层级较多但都归属于同一个确定的组件树时,推荐使用@Provide/@Consume传递数据,使用@Watch装饰器监听数据变化,在监听回调中执行业务逻辑。

        2、使用自定义事件发布订阅

        当组件关系复杂或跨越层级过多时,推荐使用EventHub或者Emitter自定义事件发布订阅的方式。当数据源改变时发布事件,依赖该数据源的组件通过订阅事件来获取数据源的改变,完成业务逻辑的处理,从而实现组件的精准刷新。

import { ButtonComponent } from '../components/ButtonComponent';
import { ListItemComponent } from '../components/ListItemComponent';

@Entry
@Component
struct UseEmitterPublish {
  listData: string[] = ['A', 'B', 'C', 'D', 'E', 'F'];

  build() {
    Column() {
      Row() {
        Column() {
          ButtonComponent()
        }
      }

      Column() {
        Column() {
          List() {
            ForEach(this.listData, (item: string, index: number) => {
              ListItemComponent({ myItem: item, index: index })
            })
          }
          .height('100%')
          .width('100%')
          .alignListItem(ListItemAlign.Center)
        }
      }
    }
  }
}

七、总结

        状态管理是MVVM模式中十分复杂的问题,为解决其中状态和视图一致性、渲染性能体验、代码可复用性和可维护性四个问题,本文主要有以下建议点:

  • 在选择装饰器时,应理解各个装饰器的特性和共享范围,结合实际开发场景的优先级,合理选择装饰器,以确保状态和视图的一致性。
  • 在使用装饰器时,对装饰器修饰的复杂变量应进行合理拆分设计,以此减少非必要的组件渲染次数,获得更好的性能体验。
  • 在代码开发过程中,对相似的逻辑处理,应考虑其复用性合理集中处理,以此有效提升代码的可维护性和可复用性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2080836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day49 | 53. 寻宝 prim算法 与kruskal算法

语言 Java 53. 寻宝 53. 寻宝&#xff08;第七期模拟笔试&#xff09; 题目 题目描述 在世界的某个区域&#xff0c;有一些分散的神秘岛屿&#xff0c;每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路&#xff0c;方便运输。 不同岛屿之间&#xff0c…

【MATLAB学习笔记】绘图——自定义标记(Marker)形状,实现与MATLAB自带标记基本一致的功能(自适应缩放、自适应裁剪)

目录 前言自定义标记函数自定义标记函数的说明纵横比调整将图形大小按磅数设置平移标记点绘制标记点边界标记点不裁剪 拓展功能——标记点自适应绘图区的缩放绘图区缩放回调函数标记点大小自适应标记点裁剪自适应 示例基本绘图自定义标记函数的使用 总代码主函数自定义标记函数…

RKNPU2从入门到实践 --- 【4】RKNN 模型构建【使用pycharm一步一步搭建RKNN模型】

目录 前言 1.1 RKNN 初始化及对象释放 1.1.1 概念介绍 1.1.2 实际演示 1.2 RKNN 模型配置 1.2.1 概念介绍 1.2.2 实际演示 1.3 模型加载 1.3.1 概念介绍 1.3.1.1 Caffe模型加载接口 1.3.1.2 TensorFlow模型加载接口 1.3.1.3 TensorFlowLite 模型加载接口 1.3.…

debian12 - 修改SSH端口连接回包

文章目录 debian12 - 修改SSH端口连接回包概述笔记先猜猜回包是哪个程序回的去下载对应版本的openssh代码工程用telnet测试的效果todo 关于ssh状态为active(start)的原因END debian12 - 修改SSH端口连接回包 概述 和同学讨论问题。 他说&#xff0c;用telnet去连接SSH端口&am…

数据仓库系列10:如何处理维度表中的变化类型?

想象一下,你正在管理一个电商平台的数据仓库。突然,你发现一个重要客户的地址发生了变化。这个简单的变更可能会对你的分析产生巨大影响。如何确保你的数据仓库能够准确地反映这种变化,同时又不丢失历史信息?欢迎来到数据仓库中最具挑战性的问题之一:维度表变化的处理。 目录…

记录imbalanced_learn离线安装

1 离线安装需要3个文件&#xff1a; 文件位置&#xff1a; ‘D:\非常重要\imbalanced-learn’ 2 复制到这个路径&#xff0c;即可 ‘…/miniconda3/lib/python3.8/site-packages’

数据结构与算法——Java实现 1.初识算法 —— 二分查找

目录 一、线性查找 二、二分查找 基础版 问题1 —— 循环条件 问题2 —— ij/2有没有问题 问题3 —— 代码中都写成 < 有何好处 改动版 人生的意义就是要独自穿过悲喜 —— 24.8.27 需求:在有序数组A内&#xff0c;查找值 target&#xff0c;如果找到返回索引&#xff0c;如…

小鹏在这次发布会上有哪些黑科技呢?

在今晚举办的小鹏10年热爱之夜&小鹏MONA MO3上市发布会上&#xff0c;何小鹏宣布&#xff0c;小鹏自研图灵芯片已于8月23日流片成功。据介绍&#xff0c;小鹏图灵芯片是全球首颗同时应用在AI汽车、机器人、飞行汽车的AI芯片&#xff0c;为AI大模型定制。 该芯片采用40核心…

【STM32】时钟体系

一、时钟体系 为什么需要时钟&#xff1f; 时钟可以为系统提供精确的定时&#xff0c;比如时间显示&#xff0c;定时器&#xff0c;pwm… 为芯片各功能模块提供工作势能,使能各组管脚工作&#xff0c;如果不使能&#xff0c;管脚无法工作 同步数据传输 给单片机提供一个时…

RabbitMQ中的死信交换机?(RabbitMQ延迟队列有了解过吗)

延迟队列 延迟队列:进入队列的消息会被延迟消费的队列。 延迟队列死信交换机 TTL&#xff08;过期时间&#xff09; 延迟队列的使用场景:超时订单、限时优惠、定时发布 死信交换机 当一个队列中的消息满足下列情况之一时&#xff0c;可以成为死信(dead letter): 消费者使…

探讨Vision Pro的成本优化与设计改进之路

随着Apple Vision Pro的发布,这款革命性的头戴式显示设备凭借其创新技术和用户体验吸引了大量关注。然而,高昂的价格成为了一个不可忽视的问题,阻碍了它的普及。为了让更多消费者能够负担得起这款产品,Apple需要探索各种方法来降低成本而不牺牲用户体验。本文将总结一些关于…

医用双目放大镜行业分析:前五大厂商占有大约39.0%的市场份额

一、当前市场状况 1. 市场规模与增长趋势 - 目前医用双目放大镜市场呈现出稳定增长的态势。据报告显示&#xff0c;预计到 2030 年全球市场规模将达到 5.2 亿美元&#xff0c;年复合增长率为 7.8%&#xff0c;这表明该行业具有较大的发展潜力。 - 增长的动力主要来自医疗行业…

排序算法(冒泡、插入、选择、快排、归并)原理动画及Python、Java实现

排序算法&#xff08;冒泡、插入、选择、快排、归并&#xff09;原理动画及Python、Java实现 1 冒泡排序1.1 原理1.2 Python、Java实现 2 插入排序2.1 原理2.2 Python、Java实现 3 选择排序3.1 原理3.2 Python、Java实现 4 快速排序4.1 原理4.2 Python、Java实现 5 归并排序5.1…

【机器学习】独立成分分析的基本概念、应用领域、具体实例(含python代码)以及ICA和PCA的联系和区别

引言 独立成分分析&#xff08;Independent Component Analysis&#xff0c;简称ICA&#xff09;是一种统计方法&#xff0c;用于从多个观察到的混合信号中提取出原始的独立信号源 文章目录 引言一、独立成分分析1.1 定义1.2 独立成分分析的基本原理1.3 独立成分分析的步骤1.3.…

RASA使用长文记录以及一些bug整理

RASA 学习笔记整理 一 安装 在虚拟环境中安装&#xff0c;进入python3版本的环境 conda activate python3 ai04机器旧版本&#xff1a;rasa-nlu和rasa-core是分开安装的 最新版本&#xff1a;rasa 将二者做了合并 直接安装 pip3 install rasa 在安装到如下步骤时候会报…

读软件开发安全之道:概念、设计与实施11安全地编程

1. 安全地编程 1.1. 在一个完整的软件设计过程中&#xff0c;我们要在创建和审查时就将安全性放在心中&#xff0c;但这只是产品开发过程的开始&#xff0c;接下来是实现、测试、部署、运行、监控、维护&#xff0c;并最终在生命周期结束时将其淘汰 1.2. 开发人员不仅必须忠实…

Android Launcher启动过程

## Launcher的启动流程&#xff1a; 1.Zygote进程 –> SystemServer进程 –> startOtherService方法 –> ActivityManagerService的systemReady方法 –> startHomeActivityLocked方法 –> ActivityStackSupervisor的startHomeActivity方法 –> 执行Activity…

Java | Leetcode Java题解之第380题O(1)时间插入、删除和获取随机元素

题目&#xff1a; 题解&#xff1a; class RandomizedSet {List<Integer> nums;Map<Integer, Integer> indices;Random random;public RandomizedSet() {nums new ArrayList<Integer>();indices new HashMap<Integer, Integer>();random new Rando…

Java9模块化系统JPMS(Java Platform Module System)

引言 随着Java技术的发展&#xff0c;开发人员面临的挑战之一是如何有效地管理和组织大型项目的依赖关系。传统的类路径&#xff08;classpath&#xff09;方法虽然简单&#xff0c;但在大型项目中却难以管理&#xff0c;尤其是在面对复杂的依赖关系时。为了解决这些问题&…

Kafka入门:从零开始了解分布式流处理平台

什么是Kafka Apache Kafka是由LinkedIn公司开发&#xff0c;后来由Apache软件基金会维护的一个分布式、分区、多副本的基于ZooKeeper协调的分布式消息系统。Kafka不仅是一个消息队列&#xff0c;还是一个强大的流处理平台&#xff0c;它能够实时地处理大量数据&#xff0c;满足…