首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

HarmonyOS开发实战( Beta5版)组件复用-精准控制组件刷新范围最佳实践

  • 25-03-03 06:42
  • 2605
  • 13246
blog.csdn.net

概述

在滑动场景下,常常会对同一类自定义组件的实例进行频繁的创建与销毁。此时可以考虑通过组件复用减少频繁创建与销毁的能耗。组件复用时,可能存在许多影响组件复用效率的操作,本篇文章将重点介绍如何通过组件复用四板斧提升复用性能。

组件复用四板斧:

  • 减少组件复用的嵌套层级,如果在复用的自定义组件中再嵌套自定义组件,会存在节点构造的开销,且需要在每个嵌套的子组件中的aboutToReuse方法中实现数据的刷新,造成耗时。
  • 优化状态管理,精准控制组件刷新范围,在复用的场景下,需要控制状态变量的刷新范围,避免扩大刷新范围,降低组件复用的效率。
  • 复用组件嵌套结构会变更的场景,使用reuseId标记不同结构的组件构成,如:使用if else结构来控制组件的创建,会造成组件树结构的大幅变动,降低组件复用的效率。需使用reuseId标记不同的组件结构,提升复用性能。
  • 不要使用函数/方法作为复用组件的入参,复用时会触发组件的构造,如果函数入参中存在耗时操作,会影响复用性能。

组件复用原理机制

组件复用机制图

优化状态管理,精准控制组件刷新范围使用

1.使用AttributeUpdater精准控制组件属性的刷新,避免组件不必要的属性刷新

复用场景常用在高频的刷新场景,精准控制组件的刷新范围可以有效减少主线程渲染负载,提升滑动性能。正反例如下:

反例:

  1. @Component
  2. export struct LessEmbeddedComponent {
  3. aboutToAppear(): void {
  4. momentData.getFriendMomentFromRawfile();
  5. }
  6. build() {
  7. Column() {
  8. Text('use nothing')
  9. List({ space: ListConstants.LIST_SPACE }) {
  10. LazyForEach(momentData, (moment: FriendMoment) => {
  11. ListItem() {
  12. OneMomentNoModifier({ color: moment.color })
  13. .onClick(() => {
  14. console.log(`my id is ${moment.id}`)
  15. })
  16. }
  17. }, (moment: FriendMoment) => moment.id)
  18. }
  19. .width("100%")
  20. .height("100%")
  21. .cachedCount(5)
  22. }
  23. }
  24. }
  25. @Reusable
  26. @Component
  27. export struct OneMomentNoModifier {
  28. @State color: string | number | Resource = "";
  29. aboutToReuse(params: Record<string, Object>): void {
  30. this.color = params.color as number;
  31. }
  32. build() {
  33. Column() {
  34. Text('这是标题')
  35. Text('这是内部文字')
  36. .fontColor(this.color)// 此处使用属性直接进行刷新,会造成Text所有属性都刷新
  37. .textAlign(TextAlign.Center)
  38. .fontStyle(FontStyle.Normal)
  39. .fontSize(13)
  40. .lineHeight(30)
  41. .opacity(0.6)
  42. .margin({ top: 10 })
  43. .fontWeight(30)
  44. .clip(false)
  45. .backgroundBlurStyle(BlurStyle.NONE)
  46. .foregroundBlurStyle(BlurStyle.NONE)
  47. .borderWidth(1)
  48. .borderColor(Color.Pink)
  49. .borderStyle(BorderStyle.Solid)
  50. .alignRules({
  51. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
  52. 'left': { 'anchor': 'image', 'align': HorizontalAlign.End }
  53. })
  54. }
  55. }
  56. }

上述反例的操作中,通过aboutToReuse对fontColor状态变量更新,进而导致组件的全部属性进行刷新,造成不必要的耗时。因此可以考虑对需要更新的组件的属性,进行精准刷新,避免不必要的重绘和渲染。

noModifier1

优化前,由H:ViewPU.viewPropertyHasChanged OneMomentNoModifier color 1标签可知,OneMomentNoModifier自定义组件下的状态变量color发生变化,与之相关联的子控件数量为1,即有一个子控件发生了标脏,之后Text全部属性会进行了刷新。

此时,H:CustomNode:BuildRecycle耗时543μs,Create[Text]耗时为4μs。

noModifier2

noModifier3

正例:

  1. import { AttributeUpdater } from '@ohos.arkui.modifier';
  2. export class MyTextUpdater extends AttributeUpdater<TextAttribute> {
  3. private color: string | number | Resource = "";
  4. constructor(color: string | number | Resource) {
  5. super();
  6. this.color = color
  7. }
  8. initializeModifier(instance: TextAttribute): void {
  9. instance.fontColor(this.color) // 差异化更新
  10. }
  11. }
  12. @Component
  13. export struct UpdaterComponent {
  14. aboutToAppear(): void {
  15. momentData.getFriendMomentFromRawfile();
  16. }
  17. build() {
  18. Column() {
  19. Text('use MyTextUpdater')
  20. List({ space: ListConstants.LIST_SPACE }) {
  21. LazyForEach(momentData, (moment: FriendMoment) => {
  22. ListItem() {
  23. OneMomentNoModifier({ color: moment.color })
  24. .onClick(() => {
  25. console.log(`my id is ${moment.id}`)
  26. })
  27. }
  28. }, (moment: FriendMoment) => moment.id)
  29. }
  30. .cachedCount(5)
  31. }
  32. }
  33. }
  34. @Reusable
  35. @Component
  36. export struct OneMomentNoModifier {
  37. color: string | number | Resource = "";
  38. textUpdater: MyTextUpdater | null = null;
  39. aboutToAppear(): void {
  40. this.textUpdater = new MyTextUpdater(this.color);
  41. }
  42. aboutToReuse(params: Record<string, Object>): void {
  43. this.color = params.color as string;
  44. this.textUpdater?.attribute?.fontColor(this.color);
  45. }
  46. build() {
  47. Column() {
  48. Text('这是标题')
  49. Text('这是内部文字')
  50. .attributeModifier(this.textUpdater) // 采用attributeUpdater来对需要更新的fontColor属性进行精准刷新,避免不必要的属性刷新。
  51. .textAlign(TextAlign.Center)
  52. .fontStyle(FontStyle.Normal)
  53. .fontSize(13)
  54. .lineHeight(30)
  55. .opacity(0.6)
  56. .margin({ top: 10 })
  57. .fontWeight(30)
  58. .clip(false)
  59. .backgroundBlurStyle(BlurStyle.NONE)
  60. .foregroundBlurStyle(BlurStyle.NONE)
  61. .borderWidth(1)
  62. .borderColor(Color.Pink)
  63. .borderStyle(BorderStyle.Solid)
  64. .alignRules({
  65. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
  66. 'left': { 'anchor': 'image', 'align': HorizontalAlign.End }
  67. })
  68. }
  69. }
  70. }

上述正例的操作中,通过AttributeUpdater来对Text组件需要刷新的属性进行精准刷新,避免Text其它不需要更改的属性的刷新。

useUpdater1

优化后,在H:aboutToReuse标签下没有H:ViewPU.viewPropertyHasChanged标签,后续也没有Create[Text]标签。此时,H:CustomNode:BuildRecycle耗时415μs

优化效果

在正反例中,针对列表滑动场景中,单个列表项中Text组件字体颜色属性的修改,反例中采用了普通组件属性刷新方式实现,正例中采用了AttributeUpdater动态属性设置方式实现。

优化后的H:CustomNode:BuildRecycle OneMomentNoModifier的耗时,如下表所示:

次数反例:使用@State(单位μs)正例:使用AttributeUpdater(单位μs)
1357338
2903494
3543415
4543451
5692509
平均607441

不同设备和场景都会对数据有影响,该数据仅供参考。

所以,Trace数据证明,精准控制组件的刷新范围可以有效减少主线程渲染负载,提升滑动性能。

因为示例中仅涉及一个Text组件的属性更新,所以优化时间绝对值较小。如果涉及组件较多,性能提升会更明显。

2.使用@Link/@ObjectLink替代@Prop减少深拷贝,提升组件创建速度

在父子组件数据同步时,如果仅仅是需要父组件向子组件同步数据,不存在修改子组件的数据变化不同步给父组件的需求。建议使用@Link/@ObjectLink替代@Prop,@Prop在装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。正反例如下:

反例:

  1. @Entry
  2. @Component
  3. struct lessEmbeddedComponent {
  4. aboutToAppear(): void {
  5. getFriendMomentFromRawfile();
  6. }
  7. build() {
  8. Column() {
  9. TopBar()
  10. List({ space: ListConstants.LIST_SPACE }) {
  11. LazyForEach(momentData, (moment: FriendMoment) => {
  12. ListItem() {
  13. OneMoment({moment: moment})
  14. }
  15. }, (moment: FriendMoment) => moment.id)
  16. }
  17. .cachedCount(Constants.CACHED_COUNT)
  18. }
  19. }
  20. }
  21. @Reusable
  22. @Component
  23. export struct OneMoment {
  24. @Prop moment: FriendMoment;
  25. build() {
  26. Column() {
  27. ...
  28. Text(`${this.moment.userName}`)
  29. ...
  30. }
  31. }
  32. }
  33. export const momentData: FriendMomentsData = new FriendMomentsData();
  34. export class FriendMoment {
  35. id: string;
  36. userName: string;
  37. avatar: string;
  38. text: string;
  39. size: number;
  40. image?: string;
  41. constructor(id: string, userName: string, avatar: string, text: string, size: number, image?: string) {
  42. this.id = id;
  43. this.userName = userName;
  44. this.avatar = avatar;
  45. this.text = text;
  46. this.size = size;
  47. if (image !== undefined) {
  48. this.image = image;
  49. }
  50. }
  51. }

上述反例的操作中,父子组件之间的数据同步用了@Prop来进行,各@Prop装饰的变量在初始化时都在本地拷贝了一份数据。会增加创建时间及内存的消耗,造成性能问题。

优化前,子组件在初始化时都在本地拷贝了一份数据,BuildItem耗时7ms175μs。

useProp

正例:

  1. @Entry
  2. @Component
  3. struct lessEmbeddedComponent {
  4. @State momentData: FriendMomentsData = new FriendMomentsData();
  5. aboutToAppear(): void {
  6. getFriendMomentFromRawfile();
  7. }
  8. build() {
  9. Column() {
  10. TopBar()
  11. List({ space: ListConstants.LIST_SPACE }) {
  12. LazyForEach(momentData, (moment: FriendMoment) => {
  13. ListItem() {
  14. OneMoment({moment: moment})
  15. }
  16. }, (moment: FriendMoment) => moment.id)
  17. }
  18. .cachedCount(Constants.CACHED_COUNT)
  19. }
  20. }
  21. }
  22. @Reusable
  23. @Component
  24. export struct OneMoment {
  25. @ObjectLink moment: FriendMoment;
  26. build() {
  27. Column() {
  28. ...
  29. Text(`${this.moment.userName}`)
  30. ...
  31. }
  32. }
  33. }
  34. @Observed
  35. export class FriendMoment {
  36. id: string;
  37. userName: string;
  38. avatar: string;
  39. text: string;
  40. size: number;
  41. image?: string;
  42. constructor(id: string, userName: string, avatar: string, text: string, size: number, image?: string) {
  43. this.id = id;
  44. this.userName = userName;
  45. this.avatar = avatar;
  46. this.text = text;
  47. this.size = size;
  48. if (image !== undefined) {
  49. this.image = image;
  50. }
  51. }
  52. }

上述正例的操作中,父子组件之间的数据同步用了@ObjectLink来进行,子组件@ObjectLink包装类把当前this指针注册给父组件,会直接将父组件的数据同步给子组件,实现父子组件数据的双向同步,降低子组件创建时间和内存消耗。

优化效果

在正反例中,针对列表滑动场景,反例采用@Prop修饰的变量,来进行父子组件间的数据同步。子组件在初始化时@Prop修饰的变量,都在本地拷贝了一份数据,增加了组件创建的时间;正例采用@ObjectLink来进行父子组件间的数据同步,把当前this指针注册给父组件,减少了组件创建的时间。

优化后,子组件直接同步父组件数据,无需深拷贝,BuildItem耗时缩短为7ms1μs。

useLink

所以,Trace数据证明,使用@Link/@ObjectLink替代@Prop减少深拷贝,可以提升组件创建速度。

因为示例中仅涉及一个简单对象FriendMoment的深拷贝,所以优化时间绝对值较小。如果涉及变量较多、对象较复杂,性能提升会更明显。

3.避免对@Link/@ObjectLink/@Prop等自动更新的状态变量,在aboutToReuse方法中再进行更新

在父子组件数据同步时,如果子组件已经使用@Link/@ObjectLink/@Prop等会自动同步父子组件数据、且驱动组件刷新的状态变量。不需要再在boutToReuse方法中再进行数据更新,此操作会造成不必要的方法执行和变量更新的耗时。正反例如下:

反例:

  1. @Entry
  2. @Component
  3. struct LessEmbeddedComponent {
  4. @State momentData: FriendMomentsData = new FriendMomentsData();
  5. aboutToAppear(): void {
  6. getFriendMomentFromRawfile();
  7. }
  8. build() {
  9. Column() {
  10. TopBar()
  11. List({ space: ListConstants.LIST_SPACE }) {
  12. LazyForEach(momentData, (moment: FriendMoment) => {
  13. ListItem() {
  14. OneMoment({moment: moment})
  15. }
  16. }, (moment: FriendMoment) => moment.id)
  17. }
  18. .cachedCount(Constants.CACHED_COUNT)
  19. }
  20. }
  21. }
  22. @Reusable
  23. @Component
  24. export struct OneMoment {
  25. // 该类型的状态变量已包含自动刷新功能,不需要再重复进行刷新
  26. @ObjectLink moment: FriendMoment;
  27. // 此处aboutToReuse为多余刷新
  28. aboutToReuse(params: Record<string, Object>): void {
  29. this.moment.id = (params.moment as FriendMoment).id
  30. this.moment.userName = (params.moment as FriendMoment).userName
  31. this.moment.avatar = (params.moment as FriendMoment).avatar
  32. this.moment.text = (params.moment as FriendMoment).text
  33. this.moment.image = (params.moment as FriendMoment).image
  34. }
  35. build() {
  36. Column() {
  37. ...
  38. Text(`${this.moment.userName}`)
  39. ...
  40. }
  41. }
  42. }
  43. @Observed
  44. export class FriendMoment {
  45. id: string;
  46. userName: string;
  47. avatar: string;
  48. text: string;
  49. size: number;
  50. image?: string;
  51. constructor(id: string, userName: string, avatar: string, text: string, size: number, image?: string) {
  52. this.id = id;
  53. this.userName = userName;
  54. this.avatar = avatar;
  55. this.text = text;
  56. this.size = size;
  57. if (image !== undefined) {
  58. this.image = image;
  59. }
  60. }
  61. }

上述反例的操作中,子组件中moment变量被@ObjectLink修饰,把当前this指针注册给父组件,会直接将父组件的数据同步给子组件,实现数据刷新。重新在aboutToReuse中刷新,如果刷新涉及的变量较多、变量中成员变量复杂,可能会造成较大性能开销。

优化前,由于在复用组件OneMoment的aboutToReuse方法中,对moment变量的各个成员变量进行了刷新,aboutToReuse耗时168μs。

refresh_auto_fresh_variable

正例:

  1. @Entry
  2. @Component
  3. struct LessEmbeddedComponent {
  4. @State momentData: FriendMomentsData = new FriendMomentsData();
  5. aboutToAppear(): void {
  6. getFriendMomentFromRawfile();
  7. }
  8. build() {
  9. Column() {
  10. TopBar()
  11. List({ space: ListConstants.LIST_SPACE }) {
  12. LazyForEach(momentData, (moment: FriendMoment) => {
  13. ListItem() {
  14. OneMoment({moment: moment})
  15. }
  16. }, (moment: FriendMoment) => moment.id)
  17. }
  18. .cachedCount(Constants.CACHED_COUNT)
  19. }
  20. }
  21. }
  22. @Reusable
  23. @Component
  24. export struct OneMoment {
  25. @ObjectLink moment: FriendMoment;
  26. build() {
  27. Column() {
  28. ...
  29. Text(`${this.moment.userName}`)
  30. ...
  31. }
  32. }
  33. }
  34. @Observed
  35. export class FriendMoment {
  36. id: string;
  37. userName: string;
  38. avatar: string;
  39. text: string;
  40. size: number;
  41. image?: string;
  42. constructor(id: string, userName: string, avatar: string, text: string, size: number, image?: string) {
  43. this.id = id;
  44. this.userName = userName;
  45. this.avatar = avatar;
  46. this.text = text;
  47. this.size = size;
  48. if (image !== undefined) {
  49. this.image = image;
  50. }
  51. }
  52. }

上述正例的操作中,子组件中moment变量被@ObjectLink修饰,把当前this指针注册给父组件,会直接将父组件的数据同步给子组件,实现数据刷新。

优化效果

在正反例中,针对列表滑动场景,反例中在aboutToReuse方法中,冗余刷新了自动刷新的变量moment中的各个成员变量。正例中,利用@ObjectLink修饰的变量moment自动同步数据的特性,直接进行刷新,不在aboutToReuse方法再进行刷新。

优化后,避免在复用组件OneMoment的aboutToReuse方法中,重复刷新变量moment的各个成员变量,aboutToReuse耗时110μs。

aovid_refresh_auto_fresh_variable

所以,通过上述Trace数据证明,避免在复用组件中,对@Link/@ObjectLink/@Prop等自动更新的状态变量,在aboutToReuse方法中再进行更新。会减少aboutToReuse方法的时间,进而减少复用组件的创建时间。

因为示例中仅涉及一个简单变量moment的各成员变量的冗余刷新,所以优化时间绝对值不大。如果涉及变量较多、变量中成员变量复杂,性能提升会更明显。

最后

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

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

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


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

​

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

路线图适合人群:

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

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

HarmonyOS Next 最新全套视频教程

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

​​

总结

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

鸿蒙NEXT全套学习资料
微信名片
注:本文转载自blog.csdn.net的让开,我要吃人了的文章"https://blog.csdn.net/weixin_55362248/article/details/141862762"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top