OpenHarmony 应用全局的 UI 状态存储:AppStorage
AppStorage 是应用全局的 UI 状态存储,是和应用的进程绑定的,由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。
和 AppStorage 不同的是,LocalStorage 是页面级的,通常应用于页面内的数据共享。而 AppStorage 是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过 AppStorage 中转,才可以和 UI 交互。
本文仅介绍 AppStorage 使用场景和相关的装饰器:@StorageProp 和 @StorageLink。
概述
AppStorage 是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage 将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。
AppStorage 可以和 UI 组件同步,且可以在应用业务逻辑中被访问。
AppStorage 中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与 UI 解耦,如果希望这些数据在 UI 中使用,需要用到@StorageProp和@StorageLink。
@StorageProp
在上文中已经提到,如果要建立 AppStorage 和自定义组件的联系,需要使用 @StorageProp 和 @StorageLink 装饰器。使用 @StorageProp(key)/@StorageLink(key)装饰组件内的变量,key 标识了 AppStorage 的属性。
当自定义组件初始化的时候,会使用 AppStorage 中对应 key 的属性值将 @StorageProp(key)/@StorageLink(key)装饰的变量初始化。由于应用逻辑的差异,无法确认是否在组件初始化之前向 AppStorage 实例中存入了对应的属性,所以 AppStorage 不一定存在 key 对应的属性,因此 @StorageProp(key)/@StorageLink(key)装饰的变量进行本地初始化是必要的。
@StorageProp(key)是和 AppStorage 中 key 对应的属性建立单向数据同步,允许本地改变,但是对于 @StorageProp,本地的修改永远不会同步回 AppStorage 中,相反,如果 AppStorage 给定 key 的属性发生改变,改变会被同步给 @StorageProp,并覆盖掉本地的修改。
装饰器使用规则说明

变量的传递/访问规则说明

图 1 @StorageProp 初始化规则图示

观察变化和行为表现
观察变化
● 当装饰的数据类型为 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
@StorageLink(key)是和 AppStorage 中 key 对应的属性建立双向数据同步:
1. 本地修改发生,该修改会被回 AppStorage 中;
2. AppStorage 中的修改发生后,该修改会被同步到所有绑定 AppStorage 对应 key 的属性上,包括单向(@StorageProp 和通过 Prop 创建的单向绑定变量)、双向(@StorageLink 和通过 Link 创建的双向绑定变量)变量和其他实例(比如 PersistentStorage)。
装饰器使用规则说明

变量的传递/访问规则说明

图 2 @StorageLink 初始化规则图示

观察变化和行为表现
观察变化
● 当装饰的数据类型为 boolean、string、number 类型时,可以观察到数值的变化。
● 当装饰的数据类型为 class 或者 Object 时,可以观察到赋值和属性赋值的变化,即 Object.keys(observedObject)返回的所有属性。
● 当装饰的对象是 array 时,可以观察到数组添加、删除、更新数组单元的变化。
框架行为
1. 当 @StorageLink(key)装饰的数值改变被观察到时,修改将被同步回 AppStorage 对应属性键值 key 的属性中。
2. AppStorage 中属性键值 key 对应的数据一旦改变,属性键值 key 绑定的所有的数据(包括双向 @StorageLink 和单向 @StorageProp)都将同步修改。
3. 当 @StorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回 AppStorage 中,还会引起所属的自定义组件的重新渲染。
使用场景
从应用逻辑使用 AppStorage 和 LocalStorage
AppStorage 是单例,它的所有 API 都是静态的,使用方法类似于中 LocalStorage 对应的非静态方法。
AppStorage.setOrCreate('PropA', 47);let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let 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<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
从 UI 内部使用 AppStorage 和 LocalStorage
@StorageLink 变量装饰器与 AppStorage 配合使用,正如 @LocalStorageLink 与 LocalStorage 配合使用一样。此装饰器使用 AppStorage 中的属性创建双向数据同步。
AppStorage.setOrCreate('PropA', 47);
let storage = new LocalStorage();
storage.setOrCreate('PropA',48);@Entry(storage)
@Component
struct CompA {@StorageLink('PropA') storLink: number = 1;@LocalStorageLink('PropA') localStorLink: number = 1;build() {Column({ space: 20 }) {Text(`From AppStorage ${this.storLink}`).onClick(() => this.storLink += 1)Text(`From LocalStorage ${this.localStorLink}`).onClick(() => this.localStorLink += 1)}}
}
不建议借助 @StorageLink 的双向同步机制实现事件通知
不建议开发者使用 @StorageLink 和 AppStorage 的双向同步的机制来实现事件通知,AppStorage 是和 UI 相关的数据存储,改变会带来 UI 的刷新,相对于一般的事件通知,UI 刷新的成本较大。
TapImage 中的点击事件,会触发 AppStorage 中 tapIndex 对应属性的改变。因为 @StorageLink 是双向同步,修改会同步会 AppStorage 中,所以,所有绑定 AppStorage 的 tapIndex 自定义组件都会被通知 UI 刷新。UI 刷新带来的成本是巨大的,因此不建议开发者使用此方式来实现基本的事件通知功能。
// xxx.ets
class ViewData {title: string;uri: Resource;color: Color = Color.Black;constructor(title: string, uri: Resource) {this.title = title;this.uri = uri}
}@Entry
@Component
struct Gallery2 {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')}}
}@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 })}}
}
开发者可以使用 emit 订阅某个事件并接收事件回调,可以减少开销,增强代码的可读性。
// xxx.ets
import emitter from '@ohos.events.emitter';let NextID: number = 0;class ViewData {title: string;uri: Resource;color: Color = Color.Black;id: number;constructor(title: string, uri: Resource) {this.title = title;this.uri = urithis.id = NextID++;}
}@Entry
@Component
struct Gallery2 {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()private preIndex: number = -1build() {Column() {Grid(this.scroller) {ForEach(this.dataList, (item: ViewData) => {GridItem() {TapImage({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: ViewData) => JSON.stringify(item))}.columnsTemplate('1fr 1fr')}}
}@Component
export struct TapImage {@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 })}}
}
以上通知事件逻辑简单,也可以简化成三元表达式。
// xxx.ets
class ViewData {title: string;uri: Resource;color: Color = Color.Black;constructor(title: string, uri: Resource) {this.title = title;this.uri = uri}
}@Entry
@Component
struct Gallery2 {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')}}
}@Component
export struct TapImage {@StorageLink('tapIndex') tapIndex: number = -1;@State tapColor: Color = Color.Black;private index: number = 0;private uri: Resource = {id: 0,type: 0,moduleName: "",bundleName: ""};build() {Column() {Image(this.uri).objectFit(ImageFit.Cover).onClick(() => {this.tapIndex = this.index;}).border({width: 5,style: BorderStyle.Dotted,color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black})}}
}
限制条件
AppStorage 与PersistentStorage以及Environment配合使用时,需要注意以下几点:
● 在 AppStorage 中创建属性后,调用 PersistentStorage.persistProp()接口时,会使用在 AppStorage 中已经存在的值,并覆盖 PersistentStorage 中的同名属性,所以建议要使用相反的调用顺序,反例可见在PersistentStorage之前访问AppStorage中的属性;
● 如果在 AppStorage 中已经创建属性后,再调用 Environment.envProp()创建同名的属性,会调用失败。因为 AppStorage 已经有同名属性,Environment 环境变量不会再写入 AppStorage 中,所以建议 AppStorage 中属性不要使用 Environment 预置环境变量名。
● 状态装饰器装饰的变量,改变会引起 UI 的渲染更新,如果改变的变量不是用于 UI 更新,只是用于消息传递,推荐使用 emitter 方式。例子可见不建议借助@StorageLink的双向同步机制实现事件通知。
相关文章:
OpenHarmony 应用全局的 UI 状态存储:AppStorage
AppStorage 是应用全局的 UI 状态存储,是和应用的进程绑定的,由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。 和 AppStorage 不同的是,LocalStorage 是页面级的,通常应用于页面内的数据共享。而…...
外置告警蜂鸣器使用小坑
告警蜂鸣器调试小坑 昨天调试新产品,由于IMO、MSC组织和IEC标准规定,不能使用带红色指示灯的蜂鸣器,于是更换了个不带灯。然而奇怪的现象出现了两次短响的程序在有的页面正常,有的页面就变成一声了。搞了一天,把各种寄…...
SSO身份验证如何帮助加强密码安全性
单点登录 (SSO) 是一种身份验证服务,可帮助用户使用一组凭据快速安全地访问所有应用程序。在员工需要访问多个应用程序才能完成工作的企业环境中,每次需要访问时都必须为每个应用程序输入登录凭据,这是一个主要的烦恼来…...
JIRA 在 2024 年完全停止服务器版本支持
在服务器上的开源许可证版本已经要过期了,想去更新下。 发现,JIRA 的所有服务器版本的支持马上就要结束了。 这就意味着,如果你部署的服务器版本的 JIRA 的话,你将没有办法对服务器进行更新。 貌似,必须使用 JIRA 提供…...
Ubuntu18.04安装gdal3.4
一.依赖关系 所以,安装顺序:SQLite -> Proj -> Gdal...
C#好资源网址推荐
C#好资源网址推荐 Microsoft 官方资料 C# 文档 https://learn.microsoft.com/zh-cn/dotnet/csharp/ C# 编程指南 https://learn.microsoft.com/zh-CN/dotnet/csharp/programming-guide/ 变量 https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/language-sp…...
UE5 Python脚本自动化Sequence Key帧
前言 码上1024了,给大家分享一个UE5的脚本小功能,UE5中Sequence动态Key功能,这样我们就可以根据我们的数据动态更新了,非常实用,适合刚入门或者小白,接下来我就把整个过程分享给大家。 过程 新建一个工程…...
2023年整理的自动化测试面试题及答案
selenium中如何判断元素是否存在? 没有提供原生的方法判断元素是否存在,一般我们可以通过定位元素异常捕获的方式判断selenium中hidden或者是display = none的元素是否可以定位到?不可以,想点击的话,可以用…...
docker 命令记录
常用 docker 命令 查看 docker 系统运行状态: systemctl status docker查看 docker 版本: docker -v查看本地镜像: docker images查看本地 docker 容器: docker ps # 正在运行的 docker 容器 docker ps -a # 所有的 docke…...
二、ElasticSearch中索引库与文档操作
文章目录 二、索引库与文档2.1 mapping映射属性2.2 操作索引库2.3 文档操作 二、索引库与文档 2.1 mapping映射属性 mapping映射属性 官方网址:https://www.elastic.co/guide/en/elasticsearch/reference/7.12/dynamic-mapping.html mapping 是对索引库中文档的…...
few shot learnning笔记
课程地址 https://youtu.be/hE7eGew4eeg?siKBM0lY7eY_AdD8Wr PPT地址 https://github.com/wangshusen/DeepLearning 第一节 Few-Shot Learning Basics 第二节 Siamese Network 第三节 Pretraining Fine Tuning(以图像识别举例) 基础 support set&…...
2022最新版-李宏毅机器学习深度学习课程-P25 Spacial Transformer Layer
data augmentation/spacial transformer CNN 并不能够处理影像放大缩小,或者是旋转的问题。所以在做影像辨识的时候,往往都要做 Data Augmentation,把你的训练数据截一小块出来放大缩小、把图片旋转,CNN 才会做到好的结果。 有一个架构叫 spacial Tran…...
轻松上手,制作电子期刊就这么简单
嗨,年轻的朋友们!你是否想过用你的创意和热情来制作一本属于自己的电子期刊?现在,这个梦想已经触手可及!只需要用到这款工具即可轻松上手,就能拥有自己的电子期刊 工具:FLBOOK在线制作电子杂志平…...
网络电视机顶盒怎么样?内行揭晓网络电视机顶盒排名
网络电视机顶盒怎么样?可以说是家家户户不可或缺的部分,但很多朋友买回家发现经常死机和卡顿,究竟要如何选择才不踩坑呢?我身为业内人士给各位分享业内最新发布的网络电视机顶盒排名,跟着我一起看看哪些网络机顶盒最值…...
2023秋招华为技术岗线上面试经历
2023/10/16 个人情况:博士,预计2024年毕业,参加了2023秋招,华为应聘到3面主管面。 下面按招聘流程顺序,记录我的面试经历。因为想写详细一点的独立文章,所以想来想去还是放到CSDN上。 1. 宣讲会 宣讲会…...
保姆级VitrualBox下载ubantu
首先先到此处下载VitrualBox选择对应的配置 Oracle VM VirtualBox 下载VitrualBox的同时要下载一个Visual,支持VitrualBox运行 最新受支持的 Visual C 可再发行程序包下载 | Microsoft Learn 同时再根据下面的网址去下载Ubantu 下载好后桌面出现这两个,…...
【学习笔记】RabbitMQ-6 消息的可靠性投递2
参考资料 RabbitMQ官方网站RabbitMQ官方文档噼咔噼咔-动力节点教程 文章目录 十一、队列Queue的消息属性11.1 具体属性11.2 自动删除11.2 自定义参数11.2.1 **Message TTL** 消息存活时间11.2.2 **Auto expire** 队列自动到期时间11.2.3 **Overflow behaviour** 溢出行为11.2.4…...
黑豹程序员-知识点-写一个bat一次执行多条命令
start cmd 执行命令行窗口 /c 执行完命令就关闭,/k执行完命令不关闭 执行多条命令,命令直接使用&&连接 echo onstart cmd /k "pnpm create vite vhello --template vue && cd vhello && pnpm install && pnpm run…...
从头开始机器学习:线性回归
一、说明 本篇实现线性回归的先决知识是:基本线性代数,微积分(偏导数)、梯度和、Python (NumPy);从线性方程入手。 代码/注释存储库链接 拉曼欣德 GitHub - ramanthind02/Machine_learning_from…...
1-k8s1.23.6-底座搭建-基于docker
这里写自定义目录标题 一、服务器准备二、安装docker三、安装k8s四、安装部署dashboard 一、服务器准备 服务器准备 服务器名称服务器IP角色CPU(最低要求)内存(最低要求)master192.168.248.10master2核2Gworker1192.168.248.11node2核2Gworker2192.168.248.12node2核2G 修改ip&…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...
stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)
这是系统中断服务程序的默认处理汇编函数,如果我们没有定义实现某个中断函数,那么当stm32产生了该中断时,就会默认跑这里来了,所以我们打开了什么中断,一定要记得实现对应的系统中断函数,否则会进来一直循环…...
Qt Quick Controls模块功能及架构
Qt Quick Controls是Qt Quick的一个附加模块,提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中,这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构,与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...
