在鸿蒙Next应用开发中,合理的状态管理是确保应用性能和响应性的关键。以下是基于最佳实践的详细阐述,每个实践都包含反例分析和正例改进,并提供了相应的代码示例。
在父子组件数据传递时,如果子组件不需要改变传递过来的数据,使用@Prop装饰器会带来不必要的深拷贝开销,影响性能。
以下代码展示了一个父组件Parent
和子组件PropChild
之间的数据传递。父组件中有一个@State
修饰的testClass
数组,包含MyClass
的实例。子组件使用@Prop
接收testClass
。
// 反例 @Observed class MyClass { public num: number = 0; constructor(num: number) { this.num = num; } } @Component struct PropChild { @Prop testClass: MyClass; // @Prop会深拷贝数据 build() { Text(`PropChild testNum ${this.testClass.num}`) } } @Entry @Component struct Parent { @State testClass: MyClass[] = [new MyClass(1)]; build() { Column() { Text(`Parent testNum ${this.testClass[0].num}`) .onClick(() => { this.testClass[0].num += 1; }) // PropChild没有改变@Prop testClass: MyClass的值,但@Prop会深拷贝数据,有性能开销 PropChild({ testClass: this.testClass[0] }) } } }
将PropChild
中的@Prop
改为@ObjectLink
,避免了深拷贝,提高了性能。
// 正例 @Observed class MyClass { public num: number = 0; constructor(num: number) { this.num = num; } } @Component struct PropChild { @ObjectLink testClass: MyClass; // @ObjectLink不会深拷贝数据 build() { Text(`PropChild testNum ${this.testClass.num}`) } } @Entry @Component struct Parent { @State testClass: MyClass[] = [new MyClass(1)]; build() { Column() { Text(`Parent testNum ${this.testClass[0].num}`) .onClick(() => { this.testClass[0].num += 1; }) // 子组件不需要改变数据时,使用@ObjectLink性能更好 PropChild({ testClass: this.testClass[0] }) } } }
开发者不应通过自定义UI状态变量来更新未被装饰为状态变量的常规变量,因为在ArkUI中,UI更新应由框架自动检测状态变量的更改来实现。
在MyComponent
组件中,realStateArr
和realState
未被装饰为状态变量,改变它们的值不会触发UI刷新,而通过needsUpdate
状态变量来带动它们的更新,这种方式不合理且性能差。
// 反例 @Entry @Component struct MyComponent { @State needsUpdate: boolean = true; realStateArr: Array<number> = [4, 1, 3, 2]; // 未使用状态变量装饰器 realState: Color = Color.Yellow; updateUIArr(param: Array<number>): Array<number> { const triggerAGet = this.needsUpdate; return param; } updateUI(param: Color): Color { const triggerAGet = this.needsUpdate; return param; } build() { Column({ space: 20 }) { ForEach(this.updateUIArr(this.realStateArr), (item: Array<number>) => { Text(`${item}`) }) Text("add item") .onClick(() => { // 改变realStateArr不会触发UI视图更新 this.realStateArr.push(this.realStateArr[this.realStateArr.length - 1] + 1); // 触发UI视图更新 this.needsUpdate =!this.needsUpdate; }) Text("chg color") .onClick(() => { // 改变realState不会触发UI视图更新 this.realState = this.realState == Color.Yellow? Color.Red : Color.Yellow; // 触发UI视图更新 this.needsUpdate =!this.needsUpdate; }) }.backgroundColor(this.updateUI(this.realState)) .width(200).height(500) } }
将realStateArr
和realState
用@State
装饰,使其成为状态变量,改变它们的值就能直接触发UI更新。
// 正例 @Entry @Component struct CompA { @State realStateArr: Array<number> = [4, 1, 3, 2]; @State realState: Color = Color.Yellow; build() { Column({ space: 20 }) { ForEach(this.realStateArr, (item: Array<number>) => { Text(`${item}`) }) Text("add item") .onClick(() => { // 改变realStateArr触发UI视图更新 this.realStateArr.push(this.realStateArr[this.realStateArr.length - 1] + 1); }) Text("chg color") .onClick(() => { // 改变realState触发UI视图更新 this.realState = this.realState == Color.Yellow? Color.Red : Color.Yellow; }) }.backgroundColor(this.realState) .width(200).height(500) } }
将同一状态变量绑定到多个同级组件的属性上,当状态变量改变时,所有关联组件都会刷新,即使它们的变化相同,这可能导致不必要的组件刷新,影响性能。将状态变量绑定到父组件上可以减少需要刷新的组件数,提高性能。
在Page
组件中,translateObj
的translateX
属性被多个同级子组件(Title
中的Image
和Text
、Stack
、Button
)绑定,当translateX
变化时,所有这些组件都会刷新。
// 反例 @Observed class Translate { translateX: number = 20; } @Component struct Title { @ObjectLink translateObj: Translate; build() { Row() { // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 Image($r('app.media.icon')) .width(50) .height(50) .translate({ x: this.translateObj.translateX // this.translateObj.translateX绑定在Image和Text组件上 }) 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绑定在Stack和Button组件上 }) Button("move") .translate({ x: this.translateObj.translateX }) .onClick(() => { animateTo({ duration: 50 }, () => { this.translateObj.translateX = (this.translateObj.translateX + 50) % 150 }) }) } } }
将子组件共同的translate
属性统一设置在父组件Column
上,减少了状态变量关联的组件数。
// 正例 @Observed class Translate { translateX: number = 20; } @Component struct Title { build() { Row() { // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 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({ // 子组件Stack和Button设置了同一个translate属性,统一到Column上设置 x: this.translateObj.translateX }) } }
当一个复杂对象被定义为状态变量时,其任何成员属性的变化都会导致关联的所有组件刷新,即使部分组件未使用该改变的属性,这会造成“冗余刷新”,影响性能。
合理拆分复杂对象,控制其关联的组件数量,避免不必要的组件刷新。具体可参考相关文章(如文档中提到的精准控制组件的更新范围和状态管理合理使用开发指导)。
在应用开发中,可以通过HiDumper查看状态变量关联的组件数,以进行性能优化。具体操作可参考状态变量组件定位工具实践。
在循环逻辑中频繁读取状态变量会影响性能,因为每次读取都可能触发相关的更新机制。
在Index
组件中,onClick
事件的for
循环里每次都读取@State message
状态变量,这会影响性能。
// 反例 import hilog from '@ohos.hilog'; @Entry @Component struct Index { @State message: string = ''; build() { Column() { Button('点击打印日志') .onClick(() => { for (let i = 0; i < 10; i++) { hilog.info(0x0000, 'TAG', '%{public}s', this.message); } }) .width('90%') .backgroundColor(Color.Blue) .fontColor(Color.White) .margin({ top: 10 }) } .justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Center) .margin({ top: 15 }) } }
在循环外先读取状态变量到临时变量,然后在循环中使用临时变量,减少了对状态变量的读取次数,提高了性能。
// 正例 import hilog from '@ohos.hilog'; @Entry @Component struct Index { @State message: string = ''; build() { Column() { Button('点击打印日志') .onClick(() => { let logMessage: string = this.message; for (let i = 0; i < 10; i++) { hilog.info(0x0000, 'TAG', '%{public}s', logMessage); } }) .width('90%') .backgroundColor(Color.Blue) .fontColor(Color.White) .margin({ top: 10 }) } .justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Center) .margin({ top: 15 }) } }
直接对状态变量赋值会多次触发ArkUI的查询和渲染行为,因为每次赋值都被视为状态变量的变化,这会影响性能。
在Index
组件的appendMsg
方法中直接操作@State message
状态变量,多次触发计算函数,增加了ArkUI不必要的查询和渲染,性能较差。
// 反例 import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'; @Entry @Component struct Index { @State message: string = ''; appendMsg(newMsg: string) { // 性能打点 hiTraceMeter.startTrace('StateVariable', 1); this.message += newMsg; this.message += ';'; this.message += '<br/>'; hiTraceMeter.finishTrace('StateVariable', 1); } 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 }) } }
使用临时变量进行数据计算,最后再将计算结果赋值给状态变量,减少了ArkUI不必要的行为,提高了性能。
// 正例 import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'; @Entry @Component struct Index { @State message: string = ''; appendMsg(newMsg: string) { // 性能打点 hiTraceMeter.startTrace('TemporaryVariable', 2); let message = this.message; message += newMsg; message += ';'; message += '<br/>'; this.message = message; hiTraceMeter.finishTrace('TemporaryVariable', 2); } 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 }) } }
通过遵循这些状态管理最佳实践,鸿蒙Next开发者能够优化应用性能,提升用户体验,确保应用在各种场景下都能高效运行。