1 前言
上章节第九章 ArkTS语言UI范式-状态管理(一)我们了解了状态管理是什么,分别有哪些状态管理,并介绍了组件内状态管理的相关知识,本章节接着上一章节的内容,我们来继续学习应用状态管理
和其他状态管理
的相关知识。
2 应用状态的装饰器
上一个章节中介绍的装饰器仅能在页面内,即一个组件树上共享状态变量。如果开发者要实现应用级
的,或者多个页面的状态数据共享
,就需要用到应用级别的状态管理
的概念。ArkTS
根据不同特性,提供了多种应用状态管理的能力:
1. LocalStorage
:页面级UI状态存储,通常用于UIAbility内
、页面间
的状态共享。
2. AppStorage
:特殊的单例LocalStorage
对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储
;
3. PersistentStorage
:持久化存储UI状态
,通常和AppStorage
配合使用,选择AppStorage
存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;
4. Environment
:应用程序运行的设备的环境参数,环境参数会同步到AppStorage
中,可以和AppStorage
搭配使用。
2.1 LocalStorage:页面级UI状态存储
LocalStorage
是页面级
的UI状态存储
,通过@Entry
装饰器接收的参数可以在页面内共享同一个LocalStorage实例
。LocalStorage
支持UIAbility
实例内多个页面间状态共享。
LocalStorage
使用场景和相关的装饰器:单向同步@LocalStorageProp
、双向同步@LocalStorageLink
@LocalStorageProp
:@LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系
。@LocalStorageLink
:@LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系
。
如果要建立LocalStorage
和自定义组件
的联系,需要使用@LocalStorageProp
和@LocalStorageLink
装饰器。使用@LocalStorageProp(key)/@LocalStorageLink(key)
装饰组件内的变量,key
标识了LocalStorage
的属性。
当自定义组件初始化的时候,@LocalStorageProp(key)/@LocalStorageLink(key)
装饰的变量会通过给定的key
,绑定LocalStorage
对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage
一定存在给定的key
(这取决于应用逻辑是否在组件初始化之前在LocalStorage
实例中存入对应的属性)。
2.1.1 @LocalStorageProp
@LocalStorageProp(key)
是和LocalStorage
中key
对应的属性建立单向数据同步
,我们允许本地改变的发生,但是对于@LocalStorageProp
,本地的修改永远不会同步回LocalStorage
中,相反,如果LocalStorage
给定key
的属性发生改变,改变会被同步给@LocalStorageProp
,并覆盖掉本地的修改。
变量的传递/访问规则说明
2.1.2 @LocalStorageLink
如果我们需要将自定义组件的状态变量的更新同步回LocalStorage
,就需要用到@LocalStorageLink
。
@LocalStorageLink(key)
是和LocalStorage
中key
对应的属性建立双向数据同步:
本地修改发生,该修改会被写回LocalStorage
中;
LocalStorage
中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(@LocalStorageProp
和通过prop
创建的单向绑定
变量)、双向(@LocalStorageLink
和通过link
创建的双向绑定
变量)变量。
变量的传递/访问规则说明
2.1.3 应用逻辑使用
let para: Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化
let propA: number | undefined = storage.get('PropA') // propA == 47
let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = 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
2.1.4 UI内部使用
@LocalStorageProp实现单向同步
// 创建新实例并使用给定对象初始化
let para:Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storProp1: number = 1;
build() {
Column({ space: 15 }) {
// 点击后从47开始加1,只改变当前组件显示的storProp1,不会同步到LocalStorage中
Button(`Parent from LocalStorage ${this.storProp1}`)
.onClick(() => this.storProp1 += 1)
Child()
}
}
}
@Component
struct Child {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storProp2: number = 2;
build() {
Column({ space: 15 }) {
// 当CompA改变时,当前storProp2不会改变,显示47
Text(`Parent from LocalStorage ${this.storProp2}`)
}
}
}
@LocalStorageLink实现双向同步
// 构造LocalStorage实例
let para:Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink('PropA')在CompA自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47
@LocalStorageLink('PropA') storLink: number = 1;
build() {
Column() {
Text(`incr @LocalStorageLink variable`)
// 点击“incr @LocalStorageLink variable”,this.storLink加1,改变同步回storage,全局变量linkToPropA也会同步改变
.onClick(() => this.storLink += 1)
// 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。
Text(`@LocalStorageLink: ${this.storLink} - linkToPropA: ${linkToPropA.get()}`)
// 注意:这里没有同步刷新,因为storage.get<number>(‘PropA’)返回的是常规变量,常规变量的更新并不会引起Text组件的重新渲染。
Text(`playCount in LocalStorage for debug ${storage.get<number>('PropA')}`)
.width(300).height(60).fontSize(12)
}
}
}
2.1.5 多个视图中同享
如果希望LocalStorage
的实例在多个视图中共享我们应该如何操作呢?
- 创建:可以在所属
UIAbility
中创建LocalStorage
实例,并调用windowStage.loadContent
。 - 获取:在UI页面通过
getShared
接口获取在通过loadContent
共享的LocalStorage
实例。LocalStorage.getShared
只在模拟器或者实机上才有效,不能在Preview
预览器中使用。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
let param:Record<string,number> = { 'PropA': 47 };
let localStorage: LocalStorage = new LocalStorage(param);
export default class EntryAbility extends UIAbility {
storage: LocalStorage = localStorage
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}
// 通过getShared接口获取stage共享的LocalStorage实例
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct CompA {
// can access LocalStorage instance using
// @LocalStorageLink/Prop decorated variables
@LocalStorageLink('PropA') varA: number = 1;
build() {
Column() {
Text(`${this.varA}`).fontSize(50)
}
}
}
建议使用这个方式来构建LocalStorage
的实例,并且在创建LocalStorage
实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。
2.2 AppStorage:应用全局的UI状态存储
AppStorage
是应用全局
的UI状态存储
,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储
。
- LocalStorage:
页面级
,通常应用于页面内的数据共享。 - AppStorage:
应用级
的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage
和环境变量Environment
都是通过AppStorage
中转,才可以和UI交互。
下面我们来学习AppStorage
使用场景和相关的装饰器:@StorageProp
和@StorageLink
。区别类比上面LocalStorage
的装饰器@LocalStorageProp
和@LocalStorageLink
,其中Prop为单向同步
,Link为双向同步
AppStorage.setOrCreate('PropA', 47);
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let 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<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101
AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
2.2.1 从应用逻辑使用AppStorage和LocalStorage
AppStorage
是单例
,它的所有API
都是静态
的,使用类似LocalStorage
// 1.LocalStorage的基本使用
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
storage.get<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101
// 2.AppStorage的基本使用
AppStorage.setOrCreate('PropA', 47);
AppStorage.get<number>('PropA') // == 49
let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let 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
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
2.2.2 从UI内部使用AppStorage和LocalStorage
@StorageLink
变量装饰器与AppStorage
配合使用,正如@LocalStorageLink
与LocalStorage
配合使用一样。此装饰器使用AppStorage
中的属性创建双向数据同步
。
// 1.LocalStorage配合LocalStorageLink使用
let storage = new LocalStorage();
storage.setOrCreate('PropA',48);
// 2.AppStorage配合StorageLink使用
AppStorage.setOrCreate('PropA', 47);
@Entry(storage)
@Component
struct CompA {
// 对应LocalStorage中的数据
@LocalStorageLink('PropA') localStorLink: number = 1;
// 对应AppStorage中的数据
@StorageLink('PropA') storLink: number = 1;
build() {
Column({ space: 20 }) {
Text(`From LocalStorage ${this.localStorLink}`)
.onClick(() => this.localStorLink += 1)
Text(`From AppStorage ${this.storLink}`)
.onClick(() => this.storLink += 1)
}
}
}
2.2.3 事件通知
使用@StorageLink
和AppStorage
的双向同步的机制来实现事件通知
时要注意,不可多个页面绑定同一变量,因为AppStorage
中的变量绑定在多个不同页面的组件中
,事件通知则不一定需要通知到所有的这些组件。并且,当这些@StorageLink
装饰的变量在UI中使用时,会触发UI刷新,带来不必要的性能影响。
非要使用需要注意这两点:
- 是否需要多个页面绑定一个变量;
- 是否需要在UI中使用来刷新UI。
class ViewData {
title: string;
uri: Resource;
color: Color = Color.Black;
constructor(title: string, uri: Resource) {
this.title = title;
this.uri = uri
}
}
@Component
struct Gallery {
dataList: Array<ViewData> = [
new ViewData('flower', $r('app.media.icon')),
new ViewData('OMG', $r('app.media.icon')),
new ViewData('OMG', $r('app.media.icon'))
]
scroller: Scroller = new Scroller()
build() {
Column() {
Grid(this.scroller) {
ForEach(this.dataList, (item: ViewData, index?: number) => {
GridItem() {
TapImage({
uri: item.uri,
index: index
})
}.aspectRatio(1)
}, (item: ViewData, index?: number) => {
return JSON.stringify(item) + index;
})
}.columnsTemplate('1fr 1fr')
}
}
}
@Component
export struct TapImage {
// 全局变量,记录当前选择的index
@StorageLink('tapIndex') tapIndex: number = -1;
private index: number
private uri: Resource
build() {
Column() {
Image(this.uri)
.objectFit(ImageFit.Cover)
.onClick(() => {
this.tapIndex = this.index;
})
.border({
width: 5,
style: BorderStyle.Dotted,
color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black
})
}
}
}
相比借助@StorageLink
的双向同步机制实现事件通知
,可以使用emit
订阅某个事件并接收事件回调的方式来减少开销,增强代码的可读性。emitter
的具体使用这里不展开,可到文档中查阅使用方式。
2.3 PersistentStorage:持久化存储UI状态
前面学习了解到LocalStorage
和AppStorage
都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage
。
PersistentStorage
是应用程序中的可选单例对象
。此对象的作用是持久化存储
选定的AppStorage属性
,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
2.3.1 概述
PersistentStorage
将选定的AppStorage属性
保留在设备磁盘上
。应用程序通过API
,以决定哪些AppStorage
属性应借助PersistentStorage
持久化。UI和业务逻辑不直接访问PersistentStorage
中的属性,所有属性访问都是对AppStorage
的访问,AppStorage
中的更改会自动同步到PersistentStorage
。
PersistentStorage
和AppStorage
中的属性建立双向同步
。应用开发通常通过AppStorage
访问PersistentStorage
,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage
获取和设置属性`的。
2.3.2 限制条件
PersistentStorage类型和值的限制:
- number, string, boolean, enum 等简单类型。
- 可以被JSON.stringify()和JSON.parse()重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化。
- 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
- 不支持undefined 和 null 。
持久化数据是一个相对缓慢的操作,应用程序应避免以下情况:
- 持久化大型数据集。
- 持久化经常变化的变量。
PersistentStorage
的持久化变量最好是小于2kb
的数据,不要大量的数据持久化,因为PersistentStorage
写入磁盘的操作
是同步
的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。
PersistentStorage
和UIContext
相关联,需要在UIContext
明确的时候才可以调用,可以通过在runScopedTask
里明确上下文。如果没有在UIContext明确的地方调用,将导致无法持久化数据。
2.3.3 实例
// 初始化PersistentStorage
PersistentStorage.PersistProp('aProp', 47);
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
// 在组件内部定义
@StorageLink('aProp') aProp: number = 48
aboutToAppear() {
// 在AppStorage获取对应属性
var aProp = AppStorage.Get<number>('aProp'); // returns 47
}
build() {
Row() {
Column() {
Text(this.message)
// 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
Text(`${this.aProp}`)
.onClick(() => {
// @StorageLink装饰的变量是和AppStorage中建立双向同步的,变化会被同步回AppStorage中
// 该属性已经被持久化,所以在AppStorage中“aProp”的改变会触发PersistentStorage,将新的改变写入本地磁盘。
this.aProp += 1;
})
}
}
}
}
注意:在调用PersistentStorage.persistProp
或者persistProps
之前使用接口 AppStorage.setOrCreate
去改变AppStorage
中的属性是错误的
,因为这样的调用顺序会丢失上一次应用程序运行中的属性值
2.4 Environment:设备环境查询
开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等,需要用到Environment设备环境查询。
Environment是ArkUI框架在应用程序启动时创建的单例对象。它为AppStorage提供了一系列描述应用程序运行状态的属性。Environment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型。
2.4.1 实例
// 将设备languageCode存入AppStorage中
Environment.EnvProp('languageCode', 'en');
@Entry
@Component
struct Index {
// StorageProp从AppStorage获取单向绑定的languageCode的变量
@StorageProp('languageCode') languageCode: string = 'en';
aboutToAppear() {
// 从AppStorage获取单向绑定的languageCode的变量
var lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');
if (lang.get() === 'zh') {
console.info('你好');
} else {
console.info('Hello!');
}
}
build() {
Row() {
Column() {
// 输出当前设备的languageCode
Text(this.languageCode)
}
}
}
}
同PersistentStorage
一样,Environment
需要在UIContext
明确的时候才可以调用。可以通过在runScopedTask
里明确上下文。如果没有在UIContext
明确的地方调用,将导致无法查询到设备环境数据
3 其他状态管理
除了前面章节提到的组件状态管理和应用状态管理,ArkTS
还提供了@Watch
和$$
来为开发者提供更多功能:
- @Watch用于监听状态变量的变化。
- $$运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。
3.1 @Watch装饰器:状态变量更改通知
@Watch
应用于对状态变量的监听
。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数
。@Watch
在ArkUI
框架内部判断数值有无更新使用的是严格相等(===)
,遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch
的回调
3.1.1 装饰器说明
使用及注意:
- 当观察到
状态变量的变化
(包括双向绑定的AppStorage
和LocalStorage
中对应的key
发生的变化)的时候,对应的@Watch的回调方法将被触发
; @Watch
方法在自定义组件的属性变更之后同步执行;- 如果在
@Watch
的方法里改变了其他的状态变量,也会引起状态变更和@Watch
的执行; - 在第一次初始化的时候,
@Watch
装饰的方法不会被调用,即认为初始化不是状态变量的改变
。只有在后续状态改变时,才会调用@Watch
回调方法。 - 注意
避免无限循环
。循环可能是因为在@Watch
的回调方法里直接或者间接地修改了同一个状态变量引起的。为了避免循环的产生,建议不要在@Watch
的回调方法里修改当前装饰的状态变量; - 注意
关注性能
,属性值更新函数会延迟组件的重新渲染(具体请见上面的行为表现),因此,回调函数应仅执行快速运算; - 不建议在
@Watch
函数中调用async await
,因为@Watch
设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题。
3.1.2 实例
@Entry
@Component
struct TotalView {
// Watch修饰的变量发生改变,会回调到onCountUpdated方法中
@Prop @Watch('onCountUpdated') count: number = 0;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
// propName为变量名count,值为this.count
console.log(`${propName}:${this.count}`)
this.total += this.count;
}
build() {
Column() {
// total为count的值
Text(`Total: ${this.total}`)
Button('count++')
.onClick(() => {
// 点击按钮count+1
this.count++
})
}
}
}
3.2 $$语法:内置组件双向同步
$$运算符
为系统内置组件提供TS变量的引用
,使得TS变量
和系统内置组件
的内部状态保持同步
。内部状态具体指什么取决于组件。例如,TextInput
组件的text参数
。
当前$$
支持基础类型变量,以及@State
、@Link
和@Prop
装饰的变量。
当前$$
支持的组件
$$绑定的变量变化时,会触发UI的同步刷新
3.2.1 实例
@Entry
@Component
struct TextInputExample {
@State text: string = ''
controller: TextInputController = new TextInputController()
build() {
Column({ space: 20 }) {
Text(this.text)
// $$绑定的变量变化
TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
.placeholderColor(Color.Grey)
.placeholderFont({ size: 14, weight: 400 })
.caretColor(Color.Blue)
.width(300)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
4 实践
4.1使用@ObjectLink代替@Prop减少不必要的深拷贝
在应用开发中,开发者经常会进行父子组件的数值传递
,而在不会改变子组件内状态变量值的情况下,使用@Prop
装饰状态变量会导致组件创建的耗时增加,从而影响一部分性能。
【反例】
@Observed
class ClassA {
public c: number = 0;
constructor(c: number) {
this.c = c;
}
}
@Component
struct PropChild {
@Prop testNum: ClassA; // @Prop 装饰状态变量会深拷贝
build() {
Text(`PropChild testNum ${this.testNum.c}`)
}
}
@Entry
@Component
struct Parent {
@State testNum: ClassA[] = [new ClassA(1)];
build() {
Column() {
Text(`Parent testNum ${this.testNum[0].c}`)
.onClick(() => {
this.testNum[0].c += 1;
})
// PropChild没有改变@Prop testNum: ClassA的值,所以这时最优的选择是使用@ObjectLink
PropChild({ testNum: this.testNum[0] })
}
}
}
在上文的示例中,PropChild
组件没有改变@Prop testNum: ClassA的值
,所以这时较优的选择是使用@ObjectLink
,因为@Prop会深拷贝数据
,具有拷贝的性能开销,所以这个时候@ObjectLink
是比@Link
和@Prop
更优的选择。
【正例】
@Observed
class ClassA {
public c: number = 0;
constructor(c: number) {
this.c = c;
}
}
@Component
struct PropChild {
@ObjectLink testNum: ClassA; // @ObjectLink 装饰状态变量不会深拷贝
build() {
Text(`PropChild testNum ${this.testNum.c}`)
}
}
@Entry
@Component
struct Parent {
@State testNum: ClassA[] = [new ClassA(1)];
build() {
Column() {
Text(`Parent testNum ${this.testNum[0].c}`)
.onClick(() => {
this.testNum[0].c += 1;
})
PropChild({ testNum: this.testNum[0] })
}
}
}
4.2 不使用状态变量强行更新非状态变量关联组件
【反例】
@Entry
@Component
struct CompA {
@State needsUpdate: boolean = true;
realState1: Array<number> = [4, 1, 3, 2]; // 未使用状态变量装饰器
realState2: Color = Color.Yellow;
updateUI1(param: Array<number>): Array<number> {
const triggerAGet = this.needsUpdate;
return param;
}
updateUI2(param: Color): Color {
const triggerAGet = this.needsUpdate;
return param;
}
build() {
Column({ space: 20 }) {
ForEach(this.updateUI1(this.realState1),
(item: Array<number>) => {
Text(`${item}`)
})
Text("add item")
.onClick(() => {
// 改变realState1不会触发UI视图更新
this.realState1.push(this.realState1[this.realState1.length-1] + 1);
// 触发UI视图更新
this.needsUpdate = !this.needsUpdate;
})
Text("chg color")
.onClick(() => {
// 改变realState2不会触发UI视图更新
this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
// 触发UI视图更新
this.needsUpdate = !this.needsUpdate;
})
}.backgroundColor(this.updateUI2(this.realState2))
.width(200).height(500)
}
}
上述示例存在以下问题:
-
应用程序希望控制UI更新逻辑,但在ArkUI中,UI更新的逻辑应该是由框架来检测应用程序状态变量的更改去实现。
-
this.needsUpdate是一个自定义的UI状态变量,应该仅应用于其绑定的UI组件。变量this.realState1、this.realState2没有被装饰,他们的变化将不会触发UI刷新。
但是在该应用中,用户试图通过this.needsUpdate
的更新来带动常规变量this.realState1
、this.realState2
的更新,此方法不合理
且更新性能较差
。
【正例】
要解决此问题,应将realState1
和realState2
成员变量用@State
装饰。一旦完成此操作,就不再需要变量needsUpdate
。
@Entry
@Component
struct CompA {
@State realState1: Array<number> = [4, 1, 3, 2];
@State realState2: Color = Color.Yellow;
build() {
Column({ space: 20 }) {
ForEach(this.realState1,
(item: Array<number>) => {
Text(`${item}`)
})
Text("add item")
.onClick(() => {
// 改变realState1触发UI视图更新
this.realState1.push(this.realState1[this.realState1.length-1] + 1);
})
Text("chg color")
.onClick(() => {
// 改变realState2触发UI视图更新
this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
})
}.backgroundColor(this.realState2)
.width(200).height(500)
}
}
4.3精准控制状态变量关联的组件数
精准控制状态变量关联的组件数能减少不必要的组件刷新,提高组件的刷新效率。有时开发者会将同一个状态变量绑定多个同级组件的属性,当状态变量改变时,会让这些组件做出相同的改变,这有时会造成组件的不必要刷新,如果存在某些比较复杂的组件,则会大大影响整体的性能。但是如果将这个状态变量绑定在这些同级组件的父组件上,则可以减少需要刷新的组件数,从而提高刷新的性能。
【反例】
@Observed
class Translate {
translateX: number = 20;
}
@Component
struct Title {
@ObjectLink translateObj: Translate;
build() {
Row() {
Image($r('app.media.icon'))
.width(50)
.height(50)
.translate({
x:this.translateObj.translateX // this.translateObj.translateX used in two component both in Row
})
Text("Title")
.fontSize(20)
.translate({
x: this.translateObj.translateX
})
}
}
}
@Entry
@Component
struct Page {
@State translateObj: Translate = new Translate();
build() {
Column() {
Title({
translateObj: this.translateObj
})
Stack() {
}
.backgroundColor("black")
.width(200)
.height(400)
.translate({
x:this.translateObj.translateX //this.translateObj.translateX used in two components both in Column
})
Button("move")
.translate({
x:this.translateObj.translateX
})
.onClick(() => {
animateTo({
duration: 50
},()=>{
this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
})
})
}
}
}
在上面的示例中,状态变量this.translateObj.translateX
被用在多个同级的子组件下,当this.translateObj.translateX
变化时,会导致所有关联它的组件一起刷新,但实际上由于这些组件的变化是相同的,因此可以将这个属性绑定到他们共同的父组件上,来实现减少组件的刷新数量。经过分析,所有的子组件其实都处于Page
下的Column
中,因此将所有子组件相同的translate
属性统一到Column
上,来实现精准控制状态变量关联的组件数。
【正例】
@Observed
class Translate {
translateX: number = 20;
}
@Component
struct Title {
build() {
Row() {
Image($r('app.media.icon'))
.width(50)
.height(50)
Text("Title")
.fontSize(20)
}
}
}
@Entry
@Component
struct Page1 {
@State translateObj: Translate = new Translate();
build() {
Column() {
Title()
Stack() {
}
.backgroundColor("black")
.width(200)
.height(400)
Button("move")
.onClick(() => {
animateTo({
duration: 50
},()=>{
this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
})
})
}
.translate({ // the component in Column shares the same property translate
x: this.translateObj.translateX
})
}
}
参考文献:
[1]OpenHarmoney应用开发文档