系列文章目录
文章目录
状态管理最佳实践概述
ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,更新数据的时候直接更新视图。如下图所示:
在ArkUI的开发过程中,如果没有选择合适的装饰器或合理的控制状态更新范围,可能会导致以下问题:
-
状态和UI的不一致,如同一状态的界面元素展示的UI不同,或UI界面展示的不是最新的状态。
-
非必要的UI视图刷新,如只修改局部组件状态时导致组件所在页面的整体刷新。
合理选择装饰器
避免不必要的状态变量的使用
状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化。
同时存在读写操作,并关联了组件,推荐使用状态变量
仅读取变量的值,没有任何写的操作,直接使用一般变量即可
建议使用临时变量替换状态变量
状态变量发生变化时,ArkUI会查询依赖该状态变量的组件并执行依赖该状态变量的组件的更新方法,完成组件渲染的行为。通过使用临时变量的计算代替直接操作状态变量,可以使ArkUI仅在最后一次状态变量变更时查询并渲染组件,减少不必要的行为,从而提高应用性能。
//反例
@State message: string = '';
appendMsg(newMsg: string) {
this.message += newMsg;
this.message += ';';
this.message += '<br/>';
}
//正例
@State message: string = '';
appendMsg(newMsg: string) {
let message = this.message;
message += newMsg;
message += ';';
message += '<br/>';
this.message = message;
}
最小化状态共享范围
在没有强烈的业务需求下,尽可能按照状态需要共享的最小范围选择合适的装饰器。
组件内独享的状态-使用@State装饰器
组件内独享的状态的生命周期和组件同步,状态的定义和更新都在组件内,组件销毁,状态也随即消失。
常见于界面UI元素数据,比如当前按钮是否可用、文字是否高亮等
组件间需要共享的状态
- 父子组件间共享状态
- 不同子树上组件间共享状态(共享状态loading)
- 不同组件树间共享状态
对于上述三种场景,提供了六种装饰器组合以解决不同范围内的组件间状态共享.
- @State+@Prop、@State+@Link、@State+@Observed+@ObjectLink
- @Provide+@Consume:状态共享范围是以@Provide所在组件为祖先节点的整棵子树,子树上的任意后代组件通过@Consume都可以共享同一个状态。
- LocalStorage:共享范围为UIAbility内以页面为单位的不同组件树间的共享。存储在LocalStorage中的状态的生命周期与LocalStorage绑定。LocalStorage的声明周期由应用程序决定,当应用释放最后一个指向LocalStorage的引用时,LocalStorage被垃圾回收。
- AppStorage:共享范围是应用全局。AppStorage与应用的进程绑定,由UI框架在应用程序启动时创建,当应用进程终止,AppStorage被回收。存储在AppStorage中的状态的生命周期与LocalStorage绑定。
按照软件开发原则,应优先选择共享范围能力小的装饰器方案,减少不同模块间的数据耦合,便于状态及时回收。建议选择装饰器的优先级为:@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink > @Provide+@Consume > LocalStorage > AppStorage。
//此处讲一个常见问题
//@State装饰嵌套结构的变量时,如果嵌套结构中数组元素增加或减少时界面不会刷新。
//数据结构例如
--TestDataList
└─Array
└─TestData
│——name
└─value
//由于@State装饰的变量,只能监听到对象本身的地址以及第一层属性的地址变化,
//也就是this.dataList地址的变化或者this.dataList.datas的地址变化,例如如下操作:
this.dataList = new TestDataList(...)
or
this.dataList.datas = new Array(...)
//由于this.dataList.datas.push、pop的操作不会触发dataList以及datas的地址变化,
//也就不会触发UI的刷新。
//解决措施
解决方案是利用ArkUI提供的@Observed、@ObjectLink来对嵌套的结构建立UI与数据的联系,
对于数组结构需要通过@Observed包装一层,使其成为可监听结构;
具体方案如下:
将Array变为ObseredArray,使用Observed监听。
//xxx.ets
@Observed
export class ObservedArray<T> extends Array<T> {
constructor(args?: T[]) {
if (args instanceof Array) {
super(...args);
} else {
super();
}
}
}
替换原来的Array为ObseredArray。
export class TestDataList {
datas: ObservedArray<TestData> = new ObservedArray();
}
视图中列表部分创建一个新的组件ListDataView,用ObjectLink接受数据。
@Component
struct ListDataView{
@ObjectLink datas:ObservedArray<TestData>
build() {
Column() {
ForEach(this.datas,(data:TestData)=>{
Text(data.getName())
})
}
}
}
原来的Index引入ListDataView使用,并传递参数。
import {
ObservedArray, TestData, TestDataList } from './TestData';
@Entry
@Component
struct Index {
@State dataList :TestDataList = new TestDataList()