应用全局的UI状态存储AppStorage

news2025/1/23 17:36:24

目录

1、概述

2、@StorageProp

2.1、观察变化和行为表现 

3.1、观察变化和行为表现

4、从应用逻辑使用AppStorage和LocalStorage

5、从UI内部使用AppStorage和LocalStorage

6、不建议借助@StorageLink的双向同步机制实现事件通知

6.1、推荐的事件通知方式


        AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。

        本文介绍AppStorage使用场景和相关的装饰器:@StorageProp和@StorageLink。

1、概述

        AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。

        AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。

        AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp和@StorageLink。

2、@StorageProp

        在上文中已经提到,如果要建立AppStorage和自定义组件的联系,需要使用@StorageProp和@StorageLink装饰器。使用@StorageProp(key)/@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。

        当自定义组件初始化的时候,@StorageProp(key)/@StorageLink(key)装饰的变量会通过给定的key,绑定在AppStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证AppStorage一定存在给定的key,这取决于应用逻辑,是否在组件初始化之前在AppStorage实例中存入对应的属性。

        @StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。

  • 从AppStorage的对应属性到组件的状态变量。

    组件本地的修改是允许的,但是AppStorage中给定的属性一旦发生变化,将覆盖本地的修改。

  • 被装饰的变量的初始值必须指定,如果AppStorage实例中不存在属性,则作为初始化默认值,并存入AppStorage中。

  • @StorageProp不支持从父节点初始化,只能AppStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化

2.1、观察变化和行为表现 

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化,即Object.keys(observedObject)返回的所有属性。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。

框架行为

  • 当@StorageProp(key)装饰的数值改变被观察到时,修改不会被同步回AppStorage对应属性键值key的属性中。
  • 当前@StorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他的绑定该key的数据不会同步改变。
  • 当@StorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回AppStorage中,但是会引起所属的自定义组件的重新渲染。
  • 当AppStorage中key对应的属性发生改变时,会同步给所有@StorageProp(key)装饰的数据,@StorageProp(key)本地的修改将被覆盖。

        @StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步:

  1. 本地修改发生,该修改会被写回AppStorage中;
  2. AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(@StorageProp和通过Prop创建的单向绑定变量)、双向(@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。
  • @StorageLink装饰的变量是双向同步的,从AppStorage的对应属性到自定义组件,从自定义组件到AppStorage对应属性。
  • @StorageLink被装饰变量的初始值必须指定,如果AppStorage实例中不存在属性,则作为初始化默认值,并存入AppStorage中。
3.1、观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化,即Object.keys(observedObject)返回的所有属性。
  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。

框架行为

  1. 当@StorageLink(key)装饰的数值改变被观察到时,修改将被同步回AppStorage对应属性键值key的属性中。
  2. AppStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向@StorageLink和单向@StorageProp)都将同步修改;
  3. 当@StorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回AppStorage中,还会引起所属的自定义组件的重新渲染。

4、从应用逻辑使用AppStorage和LocalStorage

        AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage对应的非静态方法。

AppStorage.SetOrCreate('PropA', 47);

let storage: LocalStorage = new LocalStorage({ 'PropA': 17 });
let propA: number = AppStorage.Get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
var link1: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link1.get() == 47
var link2: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link2.get() == 47
var prop: SubscribedAbstractProperty<number> = AppStorage.Prop('PropA'); // prop.get() == 47

link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

storage.get('PropA') // == 17 
storage.set('PropA', 101);
storage.get('PropA') // == 101

AppStorage.Get('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

5、从UI内部使用AppStorage和LocalStorage

        @StorageLink变量装饰器与AppStorage配合使用,正如@LocalStorageLink与LocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。

AppStorage.SetOrCreate('PropA', 47);
let appStorage = new LocalStorage({ 'PropA': 48 });

/**
 * 从UI内部使用AppStorage和LocalStorage
 */
@Entry(appStorage)
@Component
struct AppStorageDemo1Page {
  @StorageLink('PropA') storeLink: number = 1;
  @LocalStorageLink('PropA') localStoreLink: number = 1;

  build() {
    Column({ space: 20 }) {
      Text(`From AppStorage ${this.storeLink}`)
        .onClick(() => this.storeLink += 1)

      Text(`From LocalStorage ${this.localStoreLink}`)
        .onClick(() => {
          this.localStoreLink += 1;
        })
    }.margin({top: 48})
  }
}

        运行结果如下:

6、不建议借助@StorageLink的双向同步机制实现事件通知

        不建议开发者使用@StorageLink和AppStorage的双向同步的机制来实现事件通知,AppStorage是和UI相关的数据存储,改变会带来UI的刷新,相对于一般的事件通知,UI刷新的成本较大。

        TapImage中的点击事件,会触发AppStorage中tapIndex对应属性的改变。因为@StorageLink是双向同步,修改会同步回AppStorage中,所以,所有绑定AppStorage的tapIndex自定义组件都会被通知UI刷新。UI刷新带来的成本是巨大的,因此不建议开发者使用此方式来实现基本的事件通知功能。


class ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri
  }
}

/**
 * 事件通知的反例
 * 不建议借助@StorageLink的双向同步机制实现事件通知
 */
@Entry
@Component
struct AppStorageDemo2Page {
  dataList: Array<ViewData> = [
    new ViewData('flower', $r('app.media.icon')),
    new ViewData('OMG', $r('app.media.icon')),
    new ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: ViewData, index?: number) => {
          GridItem() {
            TapImage({
              uri: item.uri,
              index: index
            })
          }.aspectRatio(1)

        }, (item: ViewData, index?: number) => {
          return JSON.stringify(item) + index;
        })
      }.columnsTemplate('1fr 1fr')
    }
  }
}

/**
 * TapImage中的点击事件,会触发AppStorage中tapIndex对应属性的改变。
 * 因为@StorageLink是双向同步,修改会同步回AppStorage中,所以,
 * 所有绑定AppStorage的tapIndex自定义组件都会被通知UI刷新。
 * UI刷新带来的成本是巨大的,因此不建议开发者使用此方式来实现基本的事件通知功能。
 */
@Component
export struct TapImage {
  @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1;
  @State tapColor: Color = Color.Black;
  private index: number = 0;
  private uri: Resource = {
    id: 0,
    type: 0,
    moduleName: "",
    bundleName: ""
  };

  // 判断是否被选中
  onTapIndexChange() {
    if (this.tapIndex >= 0 && this.index === this.tapIndex) {
      console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, red`)
      this.tapColor = Color.Red;
    } else {
      console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, black`)
      this.tapColor = Color.Black;
    }
  }

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .onClick(() => {
          this.tapIndex = this.index;
        })
        .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
    }

  }
}
6.1、推荐的事件通知方式

        可以使用emit订阅某个事件并接收事件回调,可以减少开销,增强代码的可读性。


import emitter from '@ohos.events.emitter';

let Demo3NextID: number = 0;

class Demo2ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;
  id: number;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri
    this.id = Demo3NextID++;
  }
}

/**
 * 基于事件的方式触发UI更新(推荐)
 */
@Entry
@Component
struct AppStorageDemo3Page {
  dataList: Array<Demo2ViewData> = [
    new Demo2ViewData('flower', $r('app.media.icon')),
    new Demo2ViewData('OMG', $r('app.media.icon')),
    new Demo2ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()
  private preIndex: number = -1

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: Demo2ViewData) => {
          GridItem() {
            Demo2TapImage({
              uri: item.uri,
              index: item.id
            })
          }.aspectRatio(1)
          .onClick(() => {
            if (this.preIndex === item.id) {
              return
            }
            let innerEvent: emitter.InnerEvent = { eventId: item.id }
            // 选中态:黑变红
            let eventData: emitter.EventData = {
              data: {
                "colorTag": 1
              }
            }
            emitter.emit(innerEvent, eventData)

            if (this.preIndex != -1) {
              console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`)
              let innerEvent: emitter.InnerEvent = { eventId: this.preIndex }
              // 取消选中态:红变黑
              let eventData: emitter.EventData = {
                data: {
                  "colorTag": 0
                }
              }
              emitter.emit(innerEvent, eventData)
            }
            this.preIndex = item.id
          })
        }, (item: Demo2ViewData) => JSON.stringify(item))
      }.columnsTemplate('1fr 1fr')
    }

  }
}

@Component
 struct Demo2TapImage {
  @State tapColor: Color = Color.Black;
  private index: number = 0;
  private uri: Resource = {
    id: 0,
    type: 0,
    moduleName: "",
    bundleName: ""
  };

  onTapIndexChange(colorTag: emitter.EventData) {
    if (colorTag.data != null) {
      this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black
    }
  }

  aboutToAppear() {
    //定义事件ID
    let innerEvent: emitter.InnerEvent = { eventId: this.index }
    //注册事件
    emitter.on(innerEvent, data => {
      this.onTapIndexChange(data)
    })
  }

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
    }
  }
}

        运行结果如下:

        当用户点击其中一个图标时会展示不同颜色的边框,同时取消上一个选择的图标的红色边框 ,该推荐demo使用基于事件的通知方式触发事件通知,减少UI刷新通知的方式所带来的性能开销。

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

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

相关文章

KiCad 类型为电源输出和电源输出的引脚已连接

环境&#xff1a; KiCad 版本&#xff1a;7.0.6 操作系统版本&#xff1a;Win10 错误描述&#xff1a; KiCad 原理图 ERC 检查啊出现错误&#xff0c;错误提示下&#xff1a; 类型为电源输出和电源输出的引脚已连接。 错误原因&#xff1a; 电源输出和电源输出连接到了一起…

电商裂变营销的新策略:工会排队

电商行业已经发展了很多年了&#xff0c;一些基本的营销手段大家也是见识过的&#xff0c;比如&#xff1a;打折、满减、618、双十一、双十二等等。但是很多人把东西都屯到这种节日下单&#xff0c;算下来发现根本没便宜多少&#xff0c;有的反而更贵了&#xff0c;因为这是商家…

实在智能斩获钛媒体2023全球创新评选科技类「 大模型创新应用奖」

近日&#xff0c;历时三天的钛媒体2023 T-EDGE全球创新大会以“新视野新链接”为主题在北京隆重举办。作为科创领域全新高度的年度盛事&#xff0c;大会吸引了AI各产业链近百位海内外创投人、尖端企业家、商业领袖和国际嘉宾齐聚一堂&#xff0c;围绕新一轮AI革命、智慧数字化、…

AI时代Python量化交易实战:ChatGPT引领新时代

文章目录 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅膀》关键点内容简介作者简介购买链接 《AI时代架构师修炼之道&#xff1a;ChatGPT让架构师插上翅膀》关键点内容简介作者简介 赠书活动 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅…

【Mubert AI】快速自动生成免版税音乐

关于Mubert Mubert Mubert是一款很专业音乐创作工具&#xff0c;许多创作者和艺术家都在用它。 生成的音乐质量很高&#xff0c;使用方法也非常简单。 开始制作音乐 在主页选择“立即生成曲目”&#xff0c;无需登录立刻就可以进入音乐生成模式。 你可以根据需要&#xff0…

【模式识别】探秘判别奥秘:Fisher线性判别算法的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《模式之谜 | 数据奇迹解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1f30c;1 初识模式识…

自动生成数控加工的轨迹刀具轨迹阿基米德螺旋线(3D)

文章目录 1. 阿基米德螺旋线2. 生成步骤目标: 基于点云自动生成阿基米德螺旋线轨迹点 针对的是半球形模型效果 1. 阿基米德螺旋线 阿基米德螺旋线(Archimedean spiral)是一种数学曲线,由古希腊数学家阿基米德(Archimedes)在公元前225年左右首次研究和描述。这条曲线的方…

Ubuntu 常用命令之 clear 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 clear命令在Ubuntu系统下用于清除终端屏幕的内容。这个命令没有任何参数&#xff0c;它的主要作用就是清理终端屏幕上的所有信息&#xff0c;使得屏幕看起来像是新打开的一样。 使用clear命令非常简单&#xff0c;只需要在终端中…

微前端样式隔离、sessionStorage、localStorage隔离

1、样式隔离 前端样式不隔离&#xff0c;会产生样式冲突的问题&#xff0c;这个点在qiankun也存在 子应用1修改一个样式 button {background: red&#xff01;important&#xff1b; }其它应用也会受到影响 qiankun的css隔离方案&#xff08;shadow dom&#xff09; shadow …

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法

我在插入表数据时遇到了1366报错&#xff0c;报错内容&#xff1a;1366 - Incorrect integer value: Cindy for column name at row 1&#xff0c;下面我演示解决方法。 根据上图&#xff0c;原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…

分布式系统架构设计之分布式数据管理

随着互联网时代的不断发展&#xff0c;分布式系统架构成为支撑大规模用户和高并发访问的基础。在构建分布式系统时&#xff0c;分布式系统有着一系列的要求以及对应的核心技术&#xff0c;涉及到数据管理、通信安全性、性能优化、可扩展性设计以及架构演进与版本管理等很多方面…

[MTCTF 2022]easypickle

题目给了源码 import base64 import pickle from flask import Flask, session import os import randomapp Flask(__name__) app.config[SECRET_KEY] os.urandom(2).hex()app.route(/) def hello_world():if not session.get(user):session[user] .join(random.choices(&q…

【前端】前后端通信方法与差异(未完待续)

系列文章 【Vue】vue增加导航标签 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/134965353 【Vue】Element开发笔记 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/133947977 【Vue】vue&#xff0c;在Windows IIS平台…

Linux--编写系统服务脚本

编写一个名为myprog的系统服务脚本&#xff0c;通过位置变量s1指定的start、stop、restart、status控制参数&#xff0c;分别用来启动、停止、重启sleep进程&#xff0c;以及查看sleep进程的状态。其中&#xff0c;命令sleep用来暂停指定秒数的时间&#xff0c;这里仅用做测试&…

最新ChatGPT网站系统源码+AI绘画系统+支持GPT语音对话+详细图文搭建教程/支持GPT4.0/H5端系统/文档知识库

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

关于增强监控以检测针对Outlook Online APT活动的动态情报

一、基本内容 2023年6月&#xff0c;联邦民事行政部门&#xff08;FCEB&#xff09;在其Microsoft 365&#xff08;M365&#xff09;云环境中发现了可疑活动。该机构迅速向Microsoft和网络安全和基础设施安全局&#xff08;CISA&#xff09;报告了此情况。经过深入调查&#x…

【内存泄漏】编码实现内存泄漏检测功能

编码实现内存泄漏检测功能 使用脚本统计 meminfo 判断是否有内存泄漏 使用 bash 或 python 脚本循环抓取指定进程的 meminfo 保存到 txt 文件&#xff1b;使用 python 脚本解析出txt 文件中的 PSS 信息&#xff0c;借助 pyecharts 或其他可视化三方库将数据以折线图可视化&am…

探秘 Python 协程:解锁异步编程的超能力

概要 在 Python 的异步编程领域&#xff0c;协程&#xff08;Coroutines&#xff09;扮演了核心角色。协程提供了一种轻量级的并发编程方式&#xff0c;使得开发者能够有效地处理 I/O 密集型任务和高并发需求。本文旨在深入探讨 Python 中的协程概念&#xff0c;其底层机制及实…

XPM_CDC_SINGLE(UG974)

Parameterized Macro: Single-bit Synchronizer&#xff08;参数化宏&#xff1a;单比特同步器&#xff09; MACRO_GROUP: XPMMACRO_SUBGROUP: XPM_CDCFamilies: UltraScale, UltraScale 1、 Introduction&#xff08;介绍&#xff09; 此宏将一个一位信号从源时钟域同步到目…

MyBatis增删改查基础及其xml文件

目录 一.增删改查基础 1.增 增Insert 获取自增ID 对insert参数进行重命名 2.删 3.改 4.查 1)对MySQL查询结果进行重命名 从MySQL层面--as 从mybatis层面--Results注解 复用Results的定义 2)配置自动转换驼峰命名&#xff08;推荐&#xff09; 二.Mybatis xml配置…