HarmonyOS 鸿蒙Next状态管理AppStorageV2和PersistenceV2

发布于 1周前 作者 wuwangju 最后一次编辑是 5天前 来自 鸿蒙OS

HarmonyOS 鸿蒙Next状态管理AppStorageV2和PersistenceV2

HarmonyOS 鸿蒙Next知识点b站学习地址分享:https://www.bilibili.com/video/BV1sc411q7Bd?p=1

前言

HarmonyOS应用开发过程中,我们已经学习过了不少关于状态管理相关的技术,如

  • [@ObservedV2](/user/ObservedV2)装饰器和[@Trace](/user/Trace)装饰器:类属性变化观测
  • [@ComponentV2](/user/ComponentV2)装饰器:自定义组件
  • [@Local](/user/Local)装饰器:组件内部状态
  • [@Param](/user/Param):组件外部输入
  • [@Once](/user/Once):初始化同步一次
  • [@Event](/user/Event)装饰器:组件输出
  • [@Monitor](/user/Monitor)装饰器:状态变量修改监听
  • [@Provider](/user/Provider)装饰器和[@Consumer](/user/Consumer)装饰器:跨组件层级双向同步(没有讲过)
  • [@Computed](/user/Computed)装饰器:计算属性
  • [@Type](/user/Type)装饰器:标记类属性的类型

以上状态管理技术,都是围绕着组件内部本身的。现在要讲解的AppStorageV2PersistenceV2可以理解为应用/全局的状态管理技术。

  1. AppStorageV2是应用级别的数据管理技术,跨组件、跨页面。只要是主线程之内的UIAbility实例都可以共享数据。但是退出应用数

    据会自动销毁。

  2. PersistenceV2是应用级别的数据持久化技术,数据是直接存在设备磁盘上的,退出重新进入后,数据还存在

AppStorageV2

实际开发中,我们避免不了需要将数据实时共享在多个页面或者组件中,如个人信息,那么便可以考虑将数据存放在AppStorageV2中。

AppStorageV2是应用级别的数据管理技术,跨组件、跨页面。只要是主线程之内的UIAbility实例都可以共享数据。但是退出应用数据会自动

销毁。

AppStorageV2核心API

名称 作用
connect 创建或获取储存的数据
remove 删除指定key的储存数据
keys 返回所有AppStorageV2中的key

connect

创建或获取储存的数据

connect 说明
参数 type:指定的类型,若未指定key,则使用type的name作为key;
keyOrDefaultCreater:指定的key,或者是默认数据的构造器;
defaultCreator:默认数据的构造器。
返回值 创建或获取数据成功时,返回数据;否则返回undefined。

示例代码:

Index.ets

import { AppStorageV2, router } from '@kit.ArkUI'

@ObservedV2 export class Person { @Trace age: number = 10 }

@Entry @ComponentV2 struct Index { @Local person: Person = AppStorageV2.connect(Person, () => new Person)!

build() { Column() { Text(“A页面”) // 预览器会失效 Button(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">年龄:${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.person.age}</span></span></span></span><span class="hljs-function"><span class="hljs-params">) .onClick(() => { this.person.age++ })

  </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">Button</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(</span></span><span class="hljs-string"><span class="hljs-function"><span class="hljs-params"><span class="hljs-string">"跳转到B页面"</span></span></span></span><span class="hljs-function"><span class="hljs-params">)</span></span></span><span class="hljs-function">
    .</span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">onClick</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(() =&gt; {
      router.pushUrl({
        url: </span></span><span class="hljs-string"><span class="hljs-function"><span class="hljs-params"><span class="hljs-string">"pages/Index2"</span></span></span></span><span class="hljs-function"><span class="hljs-params">
      })
    })</span></span></span><span class="hljs-function">
}

} } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

代码解释

[@Local](/user/Local) person: Person = AppStorageV2.connect(Person, () => new Person)! 创建或者读取 keyPerson的数据。

[@Local](/user/Local) 是用来修饰person,表示person是一个状态,状态改变时,会引起UI的更新

.connect(Person,.. Person,作为connect方法的第一个参数,表示 Person类型

.connect(Person, () => new Person)!,由于connect中没有传入特定的key,那么便将Person.name视作key 。相当于

.connect(Person, Person.name,() => new Person)! 或者

.connect(Person,'Person',() => new Person)!

同时,因为有 () => new Person的作用,那么初始的数据也有了。

最后,因为 connect的返回值可能为空,因此在最后加上一个 表示非空断言

值得注意的是,以上代码在预览器上显示时,会得出 age = undefined的结果,所以需要在模拟器上测试

最后,如果你在A页面使用以上代码,修改了 age,那么B页面上也使用了age。那么便可以看到两个页面的数据是一样的。

Index.ets

import { Person } from "./Index";
import { AppStorageV2 } from '@kit.ArkUI';

@Entry @ComponentV2 struct Index2 { @Local person: Person = AppStorageV2.connect(Person, () => new Person)!

build() { Column() { Text(“B页面”) Button(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">age:${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.person.age}</span></span></span></span><span class="hljs-function"><span class="hljs-params">) } } } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

结果

remove

删除指定key的储存数据

remove 说明
参数 keyOrType:需要删除的key;如果指定的是type类型,删除的key为type的name。
返回值 无。

比如:AppStorageV2.remove(Person) 其实是等价于

AppStorageV2.remove(Person.name) 或者 AppStorageV2.remove('Person')

示例代码:

import { AppStorageV2, router } from '@kit.ArkUI'

@ObservedV2 export class Person { @Trace age: number = 10 }

@Entry @ComponentV2 struct Index { @Local person: Person = AppStorageV2.connect(Person, () => new Person)!

build() { Column({ space: 10 }) { Text(“A页面”) // 预览器会失效 Button(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">年龄:${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.person.age}</span></span></span></span><span class="hljs-function"><span class="hljs-params">) .onClick(() => { this.person.age++ })

  </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">Button</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(</span></span><span class="hljs-string"><span class="hljs-function"><span class="hljs-params"><span class="hljs-string">"跳转到B页面"</span></span></span></span><span class="hljs-function"><span class="hljs-params">)</span></span></span><span class="hljs-function">
    .</span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">onClick</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(() =&gt; {
      router.pushUrl({
        url: </span></span><span class="hljs-string"><span class="hljs-function"><span class="hljs-params"><span class="hljs-string">"pages/Index2"</span></span></span></span><span class="hljs-function"><span class="hljs-params">
      })
    })</span></span></span><span class="hljs-function">

  </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">Button</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(</span></span><span class="hljs-string"><span class="hljs-function"><span class="hljs-params"><span class="hljs-string">"删除数据啦"</span></span></span></span><span class="hljs-function"><span class="hljs-params">)</span></span></span><span class="hljs-function">
    .</span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">onClick</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(() =&gt; {
      AppStorageV2.remove(Person)
    })</span></span></span><span class="hljs-function">

}

} } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

如果此时执行了 remove方法,那么此时无论A页面中如何修改 person数据,AppStorageV2 中都不会跟随相应。B页面的perosn数据都不会受到影响。

keys

返回所有AppStorageV2中的key

示例代码

  Button("返回AppStorageV2所有的keys")
    .onClick(() => {
      console.log("AppStorageV2.keys()", AppStorageV2.keys())
    })
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

PersistenceV2

PersistenceV2是应用级别的数据持久化技术,数据是直接存在设备磁盘上的,退出重新进入后,数据还存在

PersistenceV2核心API和AppStorageV2类似,都提供了 connectremovekeys

import { PersistenceV2 } from '@kit.ArkUI'

@ObservedV2 export class Person { @Trace age: number = 10 } @Entry @ComponentV2 struct Index { @Local person: Person = PersistenceV2.connect(Person, () => new Person)!

build() { Column({ space: 10 }) { Text(“A页面”) // 预览器会失效 Button(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">年龄:${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.person.age}</span></span></span></span><span class="hljs-function"><span class="hljs-params">) .onClick(() => { this.person.age++ }) } } } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>


此时打开设备的文件目录,可以看到是实实在在把数据写到了磁盘中的。

/data/app/el2/100/base/com.example.你的包名/haps/entry/files/persistent_storage

需要注意的是,PersistenceV2还有另外的两个API

  1. save 由于非[@Trace](/user/Trace)的数据改变不会触发PersistenceV2的自动持久化,因此可以手动的调用save 进行持久化
  2. notifyOnError 表示响应序列化或反序列化失败的回调 可以做错误处理

save

手动对[@Trace](/user/Trace)的数据进行持久化

save 说明
参数 keyOrType:需要手动持久化的key;如果指定的是type类型,key为type的name。
返回值 无。

示例代码

import { PersistenceV2 } from '@kit.ArkUI'

export class Person { age: number = 10 }

@Entry @ComponentV2 struct Index { @Local person: Person = PersistenceV2.connect(Person, () => new Person)!

build() { Column({ space: 10 }) { Text(“A页面”) // 预览器会失效 Button(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">年龄:${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.person.age}</span></span></span></span><span class="hljs-function"><span class="hljs-params">) .onClick(() => { this.person.age++ console.log(“this.person.age”, this.person.age) PersistenceV2.save(Person) }) } } } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>


  1. 首次打开页面,直接修改数据,此时发现UI不会刷新,但是 日志输出却显示 age再增加(因为没有了@ObservedV2和@Trace监听)

  2. 此时退出应用,重新打开,却发现age会变成上一次增加后的数据。因为每次修改后,都调用了save方法。已经把数据持久化到磁盘中了。当重新打开页面时,会重新执行 [@Local](/user/Local) person: Person = PersistenceV2.connect(Person, () => new Person)!

    代码,便从磁盘中读取出数据渲染到UI上

notifyOnError

表示响应序列化或反序列化失败的回调 可以做错误处理

notifyOnError 说明
参数 callback:当序列化或者反序列化失败时,执行该回调;若传入undefined,取消该回调。
返回值 无。

示例代码

// 接受序列化失败的回调
PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
});
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

序列化的补充

如果要存储的数据是复杂类型嵌套的数据,需要使用 [@Type](/user/Type) 防止数据丢失

import { PersistenceV2, Type } from '@kit.ArkUI'

class Son { weight: number = 100 }

export class Person { age: number = 10 // 嵌套复杂类型,为了防止序列化数据失败,需要使用 @Type标记类型 @Type(Son) son: Son = new Son() } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

小结

  1. 如果考虑需要全局共享数据,那么可以考虑使用 AppStorageV2
  2. 如果考虑数据需要持久化,那么可以使用 PersistenceV2 ,因为是直接读写磁盘,所以不适合持久化大量数据,会导致应用性能下下降。
</markdown>
13 回复
谢谢分享经验

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

tra: TransactionList =[]
 this.transactionTable.query((result: TransactionList) => {
          this.tra= AppStorageV2.connect(TransactionList, 'transactionList', () => result)!
        })<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

模拟器运行提示错误:Error message:The type mismatches when use the key 'transactionList' in storage

是我写错了么?

如果缓存的模型带有getter的属性,会把这一部分也给缓存下来,然后反序列化过程中又找不到setter会导致程序直接崩溃,关键信息如下,看看如何解决

Stacktrace:
    at errorHelper (stateMgmt.js:10645:1)
    at getValueFromDisk (stateMgmt.js:10529:1)
    at connect (stateMgmt.js:10418:1)
    at connect (jsStateManagement.js:98:1)<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>
FIX THIS APPLICATION ERROR:  @ComponentV2 'component'[22] has error in update func: For 'xyz' key: TypeError: Cannot set property when setter is undefined<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

贴一个最小复现的代码看下

好奇问问楼主,运行文章例子代码,引入AppStorageV2会报错,怎么才能试用状态管理(V2试用版)?cke_338.png

SDK target api version是不是12的,只有最新的才支持这个

是Api12, DevEco Studio Beta1开发的。

可以点from的包,进去看d.ts文件里面有没有对应的导出,看看他的路径

回到顶部