当前位置: 首页 > news >正文

应用全局的UI状态存储AppStorage

目录

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() == 47link1.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() == 49storage.get('PropA') // == 17 
storage.set('PropA', 101);
storage.get('PropA') // == 101AppStorage.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 = urithis.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 = -1build() {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')}}
}@Componentstruct 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() {//定义事件IDlet 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刷新通知的方式所带来的性能开销。

相关文章:

应用全局的UI状态存储AppStorage

目录 1、概述 2、StorageProp 2.1、观察变化和行为表现 3、StorageLink 3.1、观察变化和行为表现 4、从应用逻辑使用AppStorage和LocalStorage 5、从UI内部使用AppStorage和LocalStorage 6、不建议借助StorageLink的双向同步机制实现事件通知 6.1、推荐的事件通知方式…...

MySQL数据库 触发器

目录 触发器概述 语法 案例 触发器概述 触发器是与表有关的数据库对象&#xff0c;指在insert/update/delete之前(BEFORE)或之后(AFTER)&#xff0c;触发并执行触发器中定义的soL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&am…...

C语言学习之给定任意的字符串,清除字符串中的空格

实例要求&#xff1a;给定任意的字符串&#xff0c;清除字符串中的空格&#xff0c;并将其输出&#xff1b;实例分析&#xff1a;1、指针函数实现&#xff0c;需要注意指针函数的返回值是一个指针类型&#xff1b;2、字符类型的数组实现&#xff0c;循环遍历并赋给新的数组&…...

由实验数据进行函数拟合的python实现

0.引言 已知公式求参的过程&#xff0c;对工程而言&#xff0c;一般是一个线性拟合或者非线性拟合的过程。我们现在来以代码片段为例&#xff0c;来描述如何求参。一般这个过程会涉及超定方程的计算。这个过程&#xff0c;原本需要使用matlab&#xff0c;现在python照样可以做…...

<JavaEE> 基于 UDP 的 Socket 通信模型

目录 一、认识相关API 1&#xff09;DatagramSocket 2&#xff09;DatagramPacket 3&#xff09;InetSocketAddress 二、UDP数据报套接字通信模型概述 三、回显客户端-服务器通信 1&#xff09;服务器代码 2&#xff09;客户端代码 一、认识相关API 1&#xff09;Data…...

Golang 链表的基础知识

文章目录 链表链表基础知识部分链表的存储方式链表的定义链表的操作性能分析 链表 更多有关于go链表的内容可以见这篇文章链表的创建和读取 链表基础知识部分 什么是链表&#xff0c;链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;…...

webpack 常见面试题

1、什么是webpack&#xff08;必会&#xff09; webpack是一个打包模块化javascript的工具&#xff0c;在webpack里一切文件皆模块&#xff0c;通过loader转换文件&#xff0c;通过plugin注入钩子&#xff0c;最后输出由多个模块组合成的文件&#xff0c;webpack专注构建模块化…...

three.js实战模拟VR全景视图

文章中使用到的案例图片都来源于&#xff1a;Humus - Textures 里面有很多免费的资源&#xff0c;可以直接下载&#xff0c;每个资源里面都提供6个不同方位的图片&#xff0c;我们通过threejs稍微处理一下&#xff0c;就能实现以下3D效果的场景了。 <template><div …...

聊聊Spring Boot配置文件:优先级顺序、bootstrap.yml与application.yml区别详解

Spring Boot 配置文件 优先级顺序 在Spring Boot中&#xff0c;配置文件的优先级顺序是&#xff1a;bootstrap.yml > application.yml > application-{profile}.yml&#xff0c;其中 {profile} 表示不同的环境配置&#xff0c;如 dev、test、prod 等。当存在相同名称的…...

Milvus向量数据库基础用法及注意细节

1、Milvus数据类型与python对应的数据类型 Milvus Python DataType.INT64 numpy.int64 DataType.INT32 numpy.int32 DataType.INT16 numpy.int16 DataType.BOOL Boolean DataType.FLOAT numpy.float32 DataType.DOUBLE numpy.double DataType.ARRAY list DataT…...

虚拟机多开怎么设置不同IP?虚拟机设置独立IP的技巧

随着虚拟化技术的不断发展&#xff0c;虚拟机已经成为了许多人的必备工具。在虚拟机中&#xff0c;我们可以轻松地创建多个虚拟机&#xff0c;并在每个虚拟机中设置不同的IP地址。下面&#xff0c;我们将介绍如何在虚拟机中设置独立IP地址的方法。 一、虚拟机多开设置不同IP的方…...

使用Docker-镜像命令

镜像名称一般分两部分组成:[repository]:[tag] 在没有指定tag时&#xff0c;默认是latest&#xff0c;代表最新版本的镜像 目录 案例一&#xff1a;从DockerHub中拉取一个nginx镜像并查看 1.1. 首先去镜像仓库搜索nginx镜像&#xff0c;比如DockerHub ​编辑 1.2.操作拉取n…...

4.3 C++对象模型和this指针

4.3 C对象模型和this指针 4.3.1 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 #include <iostream>class Person { public:Person() {mA 0;} //非静态成员变量占对象空间int mA;//静态成员变量…...

计算机网络——计算机网络的概述(一)

前言&#xff1a; 面对马上的期末考试&#xff0c;也为了以后找工作&#xff0c;需要掌握更多的知识&#xff0c;而且我们现实生活中也已经离不开计算机&#xff0c;更离不开计算机网络&#xff0c;今天开始我们就对计算机网络的知识进行一个简单的学习与记录。 目录 一、什么…...

基于多反应堆的高并发服务器【C/C++/Reactor】(中)ChannelMap 模块的实现

&#xff08;三&#xff09;ChannelMap 模块的实现 这个模块其实就是为Channel来服务的&#xff0c;前面讲了Channel这个结构体里边它封装了文件描述符。假如说我们得到了某一个文件描述符&#xff0c;需要基于这个文件描述符进行它对应的事件处理&#xff0c;那怎么办呢&…...

微信小程序实现一个音乐播放器的功能

微信小程序实现一个音乐播放器的功能 要求代码实现wxml 文件wxss 文件js文件 解析 要求 1.页面包含一个音乐列表&#xff0c;点击列表中的音乐可以播放对应的音乐。 2.播放中的音乐在列表中有标识&#xff0c;并且可以暂停或继续播放。 3.显示当前音乐的播放进度和总时长&#…...

算法基础之表达整数的奇怪方式

表达整数的奇怪方式 中国剩余定理: 求M 所有m之积 然后Mi M / mi x 如下图 满足要求 扩展中国剩余定理 找到x **使得x mod mi ai**成立 对于每两个式子 都可以推出①式 即 用扩展欧几里得算法 可以算出k1,-k2和m2–m1 判无解 : 若**(m2–m1) % d ! 0** 说明该等式无解 …...

WEB 3D技术 three.js 设置图像随窗口大小变化而变化

本文 我们来讲讲我们图层适应窗口变化的效果 可能这样说有点笼统 那么 自适应应该大家更熟悉 就是 当我们窗口发生变化说 做一些界面调整比例 例如 我们这样一个i项目界面 我们打开 F12 明显有一部分被挡住了 那么 我们可以刷新 这样是正常了 但是 我们将F12关掉 给F12的…...

实战案例:缓存不一致问题的解决(redis+本地缓存caffine)

一.问题引入 目前在写项目的时候&#xff0c;在B端查看文章&#xff0c;A端修改文章。为了增加效率&#xff0c;以及防止堆内存溢出&#xff0c;在B端选择本地缓存文章的方案。但是目前出现了A端对文章修改之后&#xff0c;B端读的还是旧数据&#xff0c;出现了缓存不一致的问…...

【开源CDP】市场增长未来的探索,开源CDP带来的技术崛起与变革

数字化趋势之下&#xff0c;数据成了企业竞争的核心资源&#xff0c;不管是公域还是私域&#xff0c;网络俨然成了品牌打响市场的一线战场&#xff0c;然而&#xff0c;在这场数字战役里&#xff0c;许多企业不得不面临一个共同问题&#xff1a;数据零散、分散、平台众多、无法…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

WebRTC调研

WebRTC是什么&#xff0c;为什么&#xff0c;如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究

摘要&#xff1a;在消费市场竞争日益激烈的当下&#xff0c;传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序&#xff0c;探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式&#xff0c;分析沉浸式体验的优势与价值…...

RabbitMQ 各类交换机

为什么要用交换机&#xff1f; 交换机用来路由消息。如果直发队列&#xff0c;这个消息就被处理消失了&#xff0c;那别的队列也需要这个消息怎么办&#xff1f;那就要用到交换机 交换机类型 1&#xff0c;fanout&#xff1a;广播 特点 广播所有消息​​&#xff1a;将消息…...

java+webstock

maven依赖 <dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.3.5</version></dependency><dependency><groupId>org.apache.tomcat.websocket</groupId&…...

mcts蒙特卡洛模拟树思想

您这个观察非常敏锐&#xff0c;而且在很大程度上是正确的&#xff01;您已经洞察到了MCTS算法在不同阶段的两种不同行为模式。我们来把这个关系理得更清楚一些&#xff0c;您的理解其实离真相只有一步之遥。 您说的“select是在二次选择的时候起作用”&#xff0c;这个观察非…...

Spring AI中使用ChatMemory实现会话记忆功能

文章目录 1、需求2、ChatMemory中消息的存储位置3、实现步骤1、引入依赖2、配置Spring AI3、配置chatmemory4、java层传递conversaionId 4、验证5、完整代码6、参考文档 1、需求 我们知道大型语言模型 &#xff08;LLM&#xff09; 是无状态的&#xff0c;这就意味着他们不会保…...