在鸿蒙Next应用开发中,合理的状态管理是确保应用性能和响应性的关键。以下是基于最佳实践的详细阐述,每个实践都包含反例分析和正例改进,并提供了相应的代码示例。
一、使用@ObjectLink代替@Prop减少不必要的深拷贝
(一)问题场景
在父子组件数据传递时,如果子组件不需要改变传递过来的数据,使用@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查看状态变量关联的组件数,以进行性能优化。具体操作可参考状态变量组件定位工具实践。
六、避免在for、while等循环逻辑中频繁读取状态变量
(一)问题场景
在循环逻辑中频繁读取状态变量会影响性能,因为每次读取都可能触发相关的更新机制。
(二)反例分析
在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开发者能够优化应用性能,提升用户体验,确保应用在各种场景下都能高效运行。