鸿蒙Next状态管理最佳实践

ops/2024/12/19 4:09:19/

在鸿蒙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组件中,realStateArrrealState未被装饰为状态变量,改变它们的值不会触发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)}
}

(三)正例改进

realStateArrrealState@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组件中,translateObjtranslateX属性被多个同级子组件(Title中的ImageTextStackButton)绑定,当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开发者能够优化应用性能,提升用户体验,确保应用在各种场景下都能高效运行。


http://www.ppmy.cn/ops/143073.html

相关文章

jvm 常用命令

jmap -dump:formatb,file 将堆内存进行文件导出&#xff0c;但是jvm为了保留数据一致性&#xff0c;可能会卡顿&#xff0c;并且导出前会进行一次fullGC&#xff0c; 实验&#xff1a;6G的最大堆应用 导出30s左右 查看gc 日志 jstat -gc pid 查看堆存活对象 jmap -histo:…

MATLAB 数据/fig图 自动保存并自动 添加时间戳后后缀

文章目录 数据保存fig图保存 数据保存 示例代码如下 % 获取当前日期和时间 currentDateTime datetime(now, Format, yyyyMMdd); dateTimeStr datestr(currentDateTime); % 替换破折号和冒号为下划线 dateTimeStr strrep(dateTimeStr, {:}, _); dateTimeStr cell2mat(date…

【Java—>Spring】短暂停顿,开往Spring的火车

大家好,我是一名Java开发工程师,多年来一直致力于Java技术的学习和实践。在我的职业生涯中,通过阅读《Java编程思想》、《Core Java》等经典著作,我对Java编程的理论基础有了全面而深入的认识。同时,在无数个日日夜夜的编码实践中,我也积累了丰富的项目经验。 今天,Java语言的…

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…

相机帧率怎样提高到最高

1.曝光时间减小 2.打开 MVS 客户端&#xff0c;在 Acquisition Control 属性下关闭帧率控制使能&#xff1a; Acquisition Frame Rate Control Enable 3. 修改相机图像格式 在 MVS 客户端的 Image Format Control 属性下&#xff0c;找到 Pixel Format 参数进行更改 黑白相机…

传统零售商商业升级的核心动机及与互联网业务融合的探索——以 AI 智能名片 S2B2C 商城小程序源码为例

摘要&#xff1a;本文旨在探讨传统零售商尝试商业升级的核心动机&#xff0c;并以 AI 智能名片 S2B2C 商城小程序源码为典型案例&#xff0c;分析互联网业务模式如何助力传统零售商转型。通过剖析传统零售增长模式的局限以及互联网业务在增长速度、迭代试错和用户需求洞察方面的…

python 获取网页表格的方法(多种方法汇总)

我们在网页上看到很多的表格&#xff0c;如果要获取里面的数据或者转化成其他格式&#xff0c; 就需要将表格获取下来并进行整理。 在Python中&#xff0c;获取网页表格的方法有多种&#xff0c;以下是一些常用的方法和库&#xff1a; 1. 使用Pandas的read_html Pandas库提…

npm或yarn包配置地址源

三种方法 1.配置.npmrc 文件 在更目录新增.npmrc文件 然后写入需要访问的包的地址 2.直接yarn.lock文件里面修改地址 简单粗暴 3.yarn install 的时候添加参数 设置包的仓库地址 yarn config set registry https://registry.yarnpkg.com 安装&#xff1a;yarn install 注意…