前提:基于官网3.1/4.0文档。参考官网文档
基于Android开发体系来进行比较和思考。(或有偏颇,自行斟酌)
装饰器(@State、@Prop等)是用于组件的状态修饰符,本篇讲的是更上一层级别,页面及应用级别。
- LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
- AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储;
- PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;
- Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。
是什么?
内存。
作用范围?
应用程序决定LocalStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。
@LocalStorageProp:@LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系。
@LocalStorageLink:@LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系。
怎么用?
let storage = new LocalStorage({ 'PropA': 47 }); // 创建新实例并使用给定对象初始化
let propA = storage.get('PropA') // propA == 47
let link1 = storage.link('PropA'); // link1.get() == 47
let link2 = storage.link('PropA'); // link2.get() == 47
let prop = storage.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
在下面的用例中,Index页面中的propA通过getShared()方法获取到共享的LocalStorage实例。点击Button跳转到Page页面,点击Change propA改变propA的值,back回Index页面后,页面中propA的值也同步修改。
// index.ets
import router from '@ohos.router';
// 通过getShared接口获取stage共享的LocalStorage实例
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct Index {
// can access LocalStorage instance using
// @LocalStorageLink/Prop decorated variables
@LocalStorageLink('PropA') propA: number = 1;
build() {
Row() {
Column() {
Text(`${this.propA}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("To Page")
.onClick(() => {
router.pushUrl({
url:'pages/Page'
})
})
}
.width('100%')
}
.height('100%')
}
}
// Page.ets
import router from '@ohos.router';
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct Page {
@LocalStorageLink('PropA') propA: number = 2;
build() {
Row() {
Column() {
Text(`${this.propA}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("Change propA")
.onClick(() => {
this.propA = 100;
})
Button("Back Index")
.onClick(() => {
router.back()
})
}
.width('100%')
}
}
}
由此可见:@LocalStorageProp
和@LocalStorageLink``修饰符其实就是获取对
LocalStorage```的权限,获取的是单项数据监听和双向数据操作的权限。
是什么?
顾名思义,相较于LocalStorage是页面及页面间的数据存储,AppStorage是进程级别。
它也有类似于@LocalStorageProp
和@LocalStorageLink
的修饰符,为:@StorageProp
和@StorageLink
修饰符。具体的作用和前述类似,只不过作用域提升到了应用级别。
关于命名,其实它定义为@AppStorageProp
和@AppStorageLink
更为统一一点,没有必要做这样的简化。
怎么用?
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
用法倒是不难理解。
AppStorage.SetOrCreate('PropA', 47);
let storage = new LocalStorage({ '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和AppStorage的双向同步的机制来实现事件通知,AppStorage是和UI相关的数据存储,改变会带来UI的刷新,相对于一般的事件通知,UI刷新的成本较大。
如果是不建议的类型,那么其实可以通过限制应用于UI 变化和编译器报错的方式来约束。一般情况下,开发为了快速进行,很可能就直接使用了。(当然程序开发,谁也无法保证完全无问题。)
是什么?
PersistentStorage和AppStorage中的属性建立双向同步。应用开发通常通过AppStorage访问PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的。
既然如此,直接使用AppStorage
不就可以了,为什么还要另外添加这样一个概念?
PersistentStorage不允许的类型和值有:
不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
不支持undefined 和 null 。
那这就纯粹是设计缺陷,实现上的能力不足了。数组应该要支持的,这都是基本的结构、属性值
PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。
PersistentStorage只能在UI页面内使用,否则将无法持久化数据。
为什么只能在UI页面使用?不是要通过AppStorage来操作的么?为什么限制在UI 线程中进行读写同步操作!!!=
怎么用?
PersistentStorage.PersistProp('aProp', 47);
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
@StorageLink('aProp') aProp: number = 48
build() {
Row() {
Column() {
Text(this.message)
// 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
Text(`${this.aProp}`)
.onClick(() => {
this.aProp += 1;
})
}
}
}
}
在PersistentStorage之后访问AppStorage中的属性
开发者可以先判断是否需要覆盖上一次保存在PersistentStorage中的值,如果需要覆盖,再调用AppStorage的接口进行修改,如果不需要覆盖,则不调用AppStorage的接口。
PersistentStorage.persistProp('aProp', 48);
if (AppStorage.get('aProp') > 50) {
// 如果PersistentStorage存储的值超过50,设置为47
AppStorage.setOrCreate('aProp',47);
}
这个设计的槽点上面已经吐过了。
如果组件中有多个状态变量,多个状态变量发生状态变化时,是否会触发多次组件初始化?如果是,那么有没有什么办法减少渲染?
一直都有个疑问,状态变量在后台发生状态值变更时,UI 是否会渲染?
PersistentStorage
的设计无法理解,因为看起来冗余且麻烦
若是想直接本地持久化的数据直接作用于UI 元素渲染,其实通过其他方式实现更简单。(譬如,注解之类,再不济包裹一层通用对象也可以的。和UI 线程直接搅在一起不是明智之举)
总的来说,能理解设计者直接打通数据驱动UI 变化的想法,但是设计、实现上面目前还有缺陷,这样会导致真正开发过程中,开发者有些场景头疼不已。(既然Android的系统设计、实现已有参考,其实鸿蒙这块就可以做得稳妥一点的。)