DEV Community

Freerain
Freerain

Posted on

鸿蒙Next状态管理优秀实践

在鸿蒙Next应用开发中,高效的状态管理对于提升应用性能至关重要。本文将介绍一些状态管理的优秀实践,帮助开发者避免常见的低效开发场景,提升应用质量。

一、使用@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] })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

正例

@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;
        })
      // 当子组件不需要发生本地改变时,优先使用@ObjectLink,因为@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候@ObjectLink是比@Link和@Prop更优的选择
      PropChild({ testNum: this.testNum[0] })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

二、不使用状态变量强行更新非状态变量关联组件

问题描述

开发者自定义UI状态变量来控制非状态变量关联组件更新,这种方式不合理且性能差。

反例

@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)
  }
}
Enter fullscreen mode Exit fullscreen mode

正例

@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)
  }
}
Enter fullscreen mode Exit fullscreen mode

三、精准控制状态变量关联的组件数

问题描述

同一个状态变量绑定多个同级组件属性,状态变量改变时会导致所有关联组件一起刷新,即使变化相同也会造成不必要刷新,影响性能。

反例

@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
          })
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

正例

@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
      })
  }
}
Enter fullscreen mode Exit fullscreen mode

四、合理控制对象类型状态变量关联的组件数量

将复杂对象定义为状态变量时,其某个成员属性变化会导致所有关联组件刷新,即使组件未直接使用该属性。建议合理拆分复杂对象,控制关联组件数量。

五、查询状态变量关联的组件数

可通过HiDumper查看状态变量关联的组件数进行性能优化,具体参考状态变量组件定位工具实践。

六、避免在for、while等循环逻辑中频繁读取状态变量

反例

@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
        })
  }
}
Enter fullscreen mode Exit fullscreen mode

正例

@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
        })
  }
}
Enter fullscreen mode Exit fullscreen mode

七、建议使用临时变量替换状态变量

反例

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
        })
  }
}
Enter fullscreen mode Exit fullscreen mode

正例

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
        })
  }
}
Enter fullscreen mode Exit fullscreen mode

通过遵循以上状态管理优秀实践,开发者可以在鸿蒙Next应用开发中更好地管理状态,提高应用性能,为用户提供更流畅的体验。

Top comments (0)