鸿蒙NEXT开发实战往期必看文章:
一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发!
“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)
HarmonyOS NEXT应用开发案例实践总结合(持续更新......)
HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)
状态管理最佳实践概述
在声明式UI编程范式中,UI是应用程序状态的函数,应用程序状态的修改会更新相应的UI界面。ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,更新数据的时候直接更新视图。如下图所示:
图1 ArkUI的MVVM模式
ArkUI提供了一系列装饰器实现ViewModel的能力,如@Prop、@Link、@Provide、LocalStorage等。当自定义组件内变量被装饰器装饰时变为状态变量,状态变量的改变会引起UI的渲染刷新。
在ArkUI的开发过程中,如果没有选择合适的装饰器或合理的控制状态更新范围,可能会导致以下问题:
1. 状态和UI的不一致,如同一状态的界面元素展示的UI不同,或UI界面展示的不是最新的状态。
2. 非必要的UI视图刷新,如只修改局部组件状态时导致组件所在页面的整体刷新。
当用户与界面产生交互行为时,状态的修改是通过事件驱动处理的。事件的处理可以在应用的任何地方,如果没有进行适当的逻辑处理管理也会导致代码冗余和不利于维护。
本文旨在从装饰器的选择、使用以及状态的逻辑处理管理方面解决以上问题,以实现更好的状态管理。
合理选择装饰器
避免不必要的状态变量的使用
删除冗余的状态变量标记
状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化。
反例1
@Observed
class Translate {
translateX: number = 20;
}
@Entry
@Component
struct MyComponent {
@State translateObj: Translate = new Translate(); // 变量translateObj没有关联任何UI组件,不应该定义为状态变量
@State buttonMsg: string = 'I am button'; // 变量buttonMsg没有关联任何UI组件,不应该定义为状态变量
build() {
}
}
以上示例中变量translateObj,buttonMsg没有关联任何UI组件,没有关联任何UI组件的状态变量不应该定义为状态变量,否则读写状态变量都会影响性能。
反例2
@Observed
class Translate {
translateX: number = 20;
}
@Entry
@Component
struct MyComponent {
@State translateObj: Translate = new Translate();
@State buttonMsg: string = 'I am button';
build() {
Column() {
Button(this.buttonMsg) // 这里只是读取变量buttonMsg的值,没有任何写的操作
}
}
}
以上示例中变量buttonMsg仅有读取操作,没有修改过,没有修改过的状态变量不应该定义为状态变量,否则读状态变量会影响性能。
正例
@Observed
class Translate {
translateX: number = 20;
}
@Entry
@Component
struct UnnecessaryState1 {
@State translateObj: Translate = new Translate(); // 同时存在读写操作,并关联了Button组件,推荐使用状态变量
buttonMsg = 'I am button'; // 仅读取变量buttonMsg的值,没有任何写的操作,直接使用一般变量即可
build() {
Column() {
Button(this.buttonMsg)
.onClick(() => {
animateTo({
duration: 50
}, () => {
this.translateObj.translateX = (this.translateObj.translateX + 50) % 150; // 点击时给变量translateObj重新赋值
})
})
}
.translate({
x: this.translateObj.translateX // 读取translateObj中的值
})
}
}
没有关联任何UI组件的状态变量和没有修改过的状态变量不应该定义为状态变量,直接使用一般变量即可,否则会影响性能。
建议使用临时变量替换状态变量
状态变量发生变化时,ArkUI会查询依赖该状态变量的组件并执行依赖该状态变量的组件的更新方法,完成组件渲染的行为。通过使用临时变量的计算代替直接操作状态变量,可以使ArkUI仅在最后一次状态变量变更时查询并渲染组件,减少不必要的行为,从而提高应用性能。状态变量行为可参考@State装饰器:组件内状态。
反例
@Entry
@Component
struct Index {
@State message: string = '';
appendMsg(newMsg: string) {
this.message += newMsg;
this.message += ';';
this.message += '<br/>';
}
build() {
Column() {
Button('点击打印日志')
.onClick(() => {
this.appendMsg('操作状态变量');
})
.width('90%')
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.margin({ top: 10})
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.margin({ top: 15 })
}
}
正例
@Entry
@Component
struct UnnecessaryState2 {
@State message: string = '';
appendMsg(newMsg: string) {
let message = this.message;
message += newMsg;
message += ';';
message += '<br/>';
this.message = message;
}
build() {
Column() {
Button('点击打印日志')
.onClick(() => {
this.appendMsg('操作临时变量');
})
.width('90%')
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.margin({ top: 10 })
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.margin({ top: 15 })
}
}
最小化状态共享范围
在没有强烈的业务需求下,尽可能按照状态需要共享的最小范围选择合适的装饰器。应用开发过程中,按照组件颗粒度,状态一般分为组件内独享的状态和组件间需要共享的状态。
组件内独享的状态
组件内独享的状态的生命周期和组件同步,状态的定义和更新都在组件内,组件销毁,状态也随即消失。常见于界面UI元素数据,比如当前按钮是否可用、文字是否高亮等。组件内独享的状态使用@State装饰器,被@State装饰器修饰后状态的修改只会触发当前组件实例的重新渲染。如下图主题列表上单个主题组件内使用@State修饰主题是否被选中的变量,当在界面点击主题时在组件内直接修改状态值。此时,只有当前主题的组件实例会重新渲染,其他主题组件不会重新渲染。
图2