赞
踩
本文主要是从页面和应用级两个方面介绍了ArkTS中的状态管理的一些概念以及如何使用。可能本文比较粗略,细节化请前往官网(【状态管理】)学习,若有理解错误的地方,欢迎评论指正。
由于下面会频繁提及到装饰器,所以来头就将会提及到了列举出来,做个简单介绍。
不管单向还是双向初始化时,都必须设置默认值,以便当LocalStorage/AppStorage中没有该key时,通过get能获取到正确的值(设定的默认值)
PS: 使用Link这种双向数据,需要注意UI渲染更新的成本。
这个比较简单,就是通过@Prop、@Link的方式由父组件传递给子组件。
@Prop:单向传递,值的复制。通过this.xxx传递到子组件,子组件通过@Prop接收状态,修改prop不会影响到夫组件
@Link: 双向传递,值的引用。通过$prop的方式来将值的引用传递给子组件。子组件通过@Link的方式接收,值改变会同步父组件改变并触发渲染。
@Provide/@Consume:用于单个组件树的上下级状态共享。类比Vue中的(Provide/inject)或者React中的上下文(Context)。上层组件通过@Provide提供状态,以该组件为root的组件树中其他下层组件都可以通过@Consume来订阅需要的状态。
根据不同的注入方式,LocalStorage可以作为UIAbility级别(跨组件)的状态共享(类似Vuex),也可以作为组件内部单个树上下层共享(类似上面的@Provide、@Consume)
在启动应用程序时,会先创建一个Stage,然后加载页面,在onWindowStageCreate生命周期(UIAbility的生命周期)中,将LocalStorage注入,这时候在LocalStorage所有的数据都可以跨组件共享。
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
para:Record<string, number> = { 'PropA': 47 };
storage: LocalStorage = new LocalStorage(this.para);
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}
全局注入之后,在组件内通过GetShared根据key获取值然后将获取到的storage通过@Entry参数的形式注入到组件内部。
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%') } } }
LocalStorage可以创建多个,就算在创建Stage的时候注入了一个全局的LocalStorage,也可以在组件内创建一个自己的LocalStorage数据库,在组件内创建然后在组件装饰器的@Entry作为参数传入,就类似@Provide、@Consume支持以这个组件为根节点的组件树共享。
let storage = new LocalStorage({ countStorage: 1 }); @Entry('storage') @Component struct Child { // 子组件实例的名字 label: string = 'no name'; // 和LocalStorage中“countStorage”的双向绑定数据 @LocalStorageLink('countStorage') playCountLink: number = 0; build() { Row() { Text(this.label) .width(50).height(60).fontSize(12) Text(`playCountLink ${this.playCountLink}: inc by 1`) .onClick(() => { this.playCountLink += 1; }) .width(200).height(60).fontSize(12) }.width(300).height(60) } }
AppStorage就是LocalStorage的一个单例,所以它所有的属性方法都是静态的,而且也可以在UI组件内部和应用级别进行状态管理。
和LocalSTorage的区别:
通过@StorageProp、@StorageLink来获取值
AppStorage.SetOrCreate('count', 1);
@Entry
@Component
struct TaskPage {
@StorageProp('count') pageCount: number = 1 // 单向,不会改变AppStorage的值
// @StorageLink('count') pageCOunt: number = 1 双向,会改变源数据
build()
{
Text('Text')
}
}
通过SetOrCreate创建变量之后,在所有的UIAbility都可以通过Get来获取数据的值。
AppStorage.SetOrCreate('count', 1);
AppStorage.Get('count') // 1
AppStorage除了上面提到的可以用于组件内/应用状态管理,主要还可以和数据持久化(PersistentStorage)和环境变量(Environment)使用,只要AppStorage中的值变化,PersistentStorage和Environment中的值也会变化,新值会覆盖旧值,并且在PersistentStorage和Environment中不能创建AppStorage已经有的属性,因为每次AppStorage更新(新增/删除变量)都会同步到PersistentStorage和Environment中,所有AppStorage中有的,其他两个中自然也有。
上面提到的AppStorage除了状态管理之外还和PersistentStorage和Environment有关,现在介绍一下PersistentStorage。
因为AppStorage是运行时保存在内存的数据,当应用关闭之后,保存的数据就被销毁了。为了将一个数据保存在用户本地,在第二次打开应用时能直接访问,所以出现了PersistentStorage。
PersistentStorage是通过写文件的方式,将数据写入文件然后保存在用户磁盘(同步写入)达到持久化的目的。
PersistentStorage是和AppStorage双向绑定的,可以理解为PersistentStorage是AppStorage的子集,通过API来设置AppStorge的哪些属性需要保持在PersistentStorage中进行持久化存储。
当应用启动之后,会先调用PersistentStorage来获取数据,如果没有则去AppStorage中查找,如果没有就在AppStorage中创建该属性并使用默认值进行初始化,然后写回到PersistentStorage。
PersistentStorage只能保存基本的简单类型(number、string、boolean、enum),并且由于写入是同步保存在用户磁盘上的,所以大小一般小于2KB。
下面代码表示:在UI内通过PersistProp(PersistentStorage初始化值的API),然后会在AppStorage上创建相同的属性(如果没有),在组件内通过@StorageLink去获取AppStorage的值(AppStorage的值和PersistentStorage值是一致的 -> 子集)
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; }) } } } }
Environment中保存的是当前设备的一些配置参数,比如国际化语言、暗黑模式…。
Environment只能保存简单的基本类型,并且是内置的环境参数,不可修改,只能访问。
在应用中一般是将我们需要的Environment配置注入到AppStorage中,然后在组件中进行访问。
@StorageProp: 值的复制,可以在组件内改变,但是不会写回到AppStorage中
@StorageLink:值的引用,会写回到Appstorage,但是不会改变Environment中的值,因为Environment是只读的。
数据更新链路:Environment -> AppStorage -> Components
下面的代码表示在UI组件内获取环境配置并使用。
// 将设备languageCode存入AppStorage中 Environment.EnvProp('languageCode', 'en'); @Entry @Component struct Index { @StorageProp('languageCode') languageCode: string = 'en'; build() { Row() { Column() { // 输出当前设备的languageCode Text(this.languageCode) } } } }
@Watch使用全等(===)的方式来对单个变量进行变化监控,需要传入一个callback,发生改变就会调用该函数,默认第一次初始化的时候不会调用(undefined->value)。可以类比Vue中的Watch监听。
@Watch是用于响应数据的监听,一般于@Prop、@Link、@StorageProp…等这些申明状态的修饰器连用
@Prop @Watch('callback') state: number = 0;
// changedPropertyName:传入的监听的属性名
// 一般当多个Watch监听状态并使用同一个callback处理的时候,可以通过该属性名分别处理
callbakc = (changedPropertyName: string) => {}
PS:
下面代码就是将自定义的组件状态isRefreshing和内置组件Refresh的refreshing双向绑定,内置组件的refreshing变化会同步自定义组件状态isRefreshing的变化。
@Entry @Component struct RefreshExample { @State isRefreshing: boolean = false @State counter: number = 0 build() { Column() { Text('Pull Down and isRefreshing: ' + this.isRefreshing) .fontSize(30) .margin(10) Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) { Text('Pull Down and refresh: ' + this.counter) .fontSize(30) .margin(10) } .onStateChange((refreshStatus: RefreshStatus) => { console.info('Refresh onStateChange state is ' + refreshStatus) }) } } }
PS:
上面我们从页面组件级别、单个UIAbility级别、整个APP应用级别简单介绍了一下状态管理的知识,可以查看下图来梳理一下,希望对大家有所帮助。
下图主要画出了整个APP的状态流程。 从书写的Js/Ts代码逻辑 -> 启动应用调用(AppStorage\LocalStorage\Persistent) -> Components组件中状态共享 -> 父子组件共享
说个题外话,有兴趣的小伙伴可以关注一下微信公粽号【大前端小册子】,以便随时随地可以一起学习探讨。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。