HarmonyOS开发实战( Beta5版)状态管理优秀实践

2024-09-04 21:28

本文主要是介绍HarmonyOS开发实战( Beta5版)状态管理优秀实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为了帮助应用程序开发人员提高其应用程序质量,特别是在高效的状态管理方面。本章节面向开发者提供了多个在开发ArkUI应用中常见的低效开发的场景,并给出了对应的解决方案。此外,还提供了同一场景下,推荐用法和不推荐用法的对比和解释说明,更直观地展示两者区别,从而帮助开发者学习如何正确地在应用开发中使用状态变量,进行高性能开发。

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

【性能对比】

使用Profiler工具分别抓取优化前后耗时(H:FlushLayoutTask)进行对比分析。

优化前@Prop耗时:

优化后@ObjectLink耗时:

组件创建耗时说明
优化前24ms273μs@Prop进行了深拷贝,耗时久
优化后16ms566μs@ObjectLink不会进行深拷贝,耗时短

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

【反例】

@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)}
}

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

建议每个状态变量关联的组件数应该少于20个。精准控制状态变量关联的组件数能减少不必要的组件刷新,提高组件的刷新效率。有时开发者会将同一个状态变量绑定多个同级组件的属性,当状态变量改变时,会让这些组件做出相同的改变,这有时会造成组件的不必要刷新,如果存在某些比较复杂的组件,则会大大影响整体的性能。但是如果将这个状态变量绑定在这些同级组件的父组件上,则可以减少需要刷新的组件数,从而提高刷新的性能。

【反例】

@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 translatex: this.translateObj.translateX})}
}

【性能对比】

使用Profiler工具分别抓取优化前后点击move按钮后页面的脏节点更新耗时(H:FlushDirtyNodeUpdate)进行对比分析。

优化前脏节点更新耗时:

优化后脏节点更新耗时:

脏节点更新耗时(局限不同设备和场景,数据仅供参考)说明
优化前2ms481μs状态变量关联的脏节点数量多,更新耗时久
优化后225μs减少了状态变量关联的脏节点数量,更新耗时短

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

如果将一个复杂对象定义为状态变量,需要合理控制其关联的组件数。当对象中某一个成员属性发生变化时,会导致该对象关联的所有组件刷新,尽管这些组件可能并没有直接使用到该改变的属性。为了避免这种“冗余刷新”对性能产生影响,建议合理拆分该复杂对象,控制对象关联的组件数量。具体可参考精准控制组件的更新范围和状态管理合理使用开发指导 两篇文章。

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

在应用开发中,可以通过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})}
}

【正例】

@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会查询依赖该状态变量的组件并执行依赖该状态变量的组件的更新方法,完成组件渲染的行为。通过使用临时变量的计算代替直接操作状态变量,可以使ArkUI仅在最后一次状态变量变更时查询并渲染组件,减少不必要的行为,从而提高应用性能。状态变量行为可参考@State装饰器:组件内状态。

【反例】

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})}
}

直接操作状态变量,三次触发计算函数,运行耗时结果如下

【正例】

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})}
}

使用临时变量取代状态变量的计算,三次触发计算函数,运行耗时结果如下

【总结】

计算方式耗时(局限不同设备和场景,数据仅供参考)说明
直接操作状态变量1.01ms增加了ArkUI不必要的查询和渲染行为,导致性能劣化
使用临时变量计算0.63ms减少了ArkUI不必要的行为,优化性能

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

GitCode - 全球开发者的开源社区,开源代码托管平台  希望这一份鸿蒙学习文档能够给大家带来帮助~


鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)       

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

这篇关于HarmonyOS开发实战( Beta5版)状态管理优秀实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137076

相关文章

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表