当前位置: 首页 > 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;数据零散、分散、平台众多、无法…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...