OpenHarmony实战开发-组件复用实践。

若开发者的应用中存在以下场景,并成为UI线程的帧率瓶颈,应该考虑使用组件复用机制提升应用性能:

  1. 滑动场景下对同一类自定义组件的实例进行频繁的创建与销毁。
  2. 反复切换条件渲染的控制分支,且控制分支中的组件子树结构比较复杂。

组件复用生效的条件是:

  1. 自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力。
  2. 在一个自定义组件(父)下创建出来的具备组件复用能力的自定义组件(子),在可复用自定义组件从组件树上移除之后,会被加入到其父自定义组件的可复用组件缓存中。
  3. 在一个自定义组件(父)下创建可复用的子组件时,若可复用子节点缓存中有对应类型的可复用子组件的实例,会通过更新可复用子组件的方式,快速创建可复用子组件。

约束限制

  1. @Reusable标识自定义组件具备可复用的能力,它可以被添加到任意的自定义组件上,但是开发者需要小心处理自定义组件的创建流程和更新流程以确保自定义组件在复用之后能展示出正确的行为。
  2. 可复用自定义组件的缓存和复用只能发生在同一父组件下,无法在不同的父组件下复用同一自定义组件的实例。例如,A组件是可复用组件,其也是B组件的子组件,并进入了B组件的可复用组件缓存中,但是在C组件中创建A组件时,无法使用B组件缓存的A组件。
  3. @Reusable装饰器只需要对复用子树的根节点进行标记。例如:自定义组件A中有一个自定义子组件B,若需要复用A与B的子树,只需要对A组件添加@Reusable装饰器。
  4. 可复用自定义组件中嵌套自定义组件,如果想要对嵌套的子组件的内容进行更新,需要实现对应子组件的aboutToReuse生命周期回调。例如:A组件是可复用的组件,B是A中嵌套的子组件,要想实现对A组件中的B组件内容进行更新,需要在B组件中实现aboutToReuse生命周期回调。
  5. 自定义组件的复用带来的性能提升主要体现在节省了自定义组件的JS对象的创建时间并复用了自定义组件的组件树结构,若应用开发者在自定义组件复用的前后使用渲染控制语法显著的改变了自定义组件的组件树结构,那么将无法享受到组件复用带来的性能提升。
  6. 组件复用仅发生在存在可复用组件从组件树上移除并再次加入到组件树的场景中,若不存在上述场景,将无法触发组件复用。例如,使用ForEach渲染控制语法创建可复用的自定义组件,由于ForEach渲染控制语法的全展开属性,不能触发组件复用。
  7. 组件复用当前不支持嵌套使用。即在可复用的组件的子树中存在可复用的组件,可能导致未定义的结果。

生命周期

可复用组件从C++侧的组件树上移除时,自定义组件在ArkUI框架native侧的CustomNode会被挂载到其对应的JSView上。复用发生之后,CustomNode被JSView引用,并触发ViewPU上的aboutToRecycle方法,ViewPU的实例将会被RecycleManager引用。

可复用组件从RecycleManager中重新加入组件树时,会调用前端ViewPU对象上的aboutToReuse生命周期回调。

接口说明

组件的生命周期回调,在可复用组件从复用缓存中加入到组件树之前调用,可在其中更新组件的状态变量以展示正确的内容,入参的类型与自定义组件的构造函数入参相同。

aboutToReuse?(params: { [key: string]: unknown }): void;

组件的生命周期回调,在可复用组件从组件树上被加入到复用缓存之前调用。

aboutToRecycle?(): void;

开发者可以使用reuseId为复用组件分配复用组,相同reuseId的组件会在同一个复用组中复用。

reuseId(id: string);

Reusable装饰器,用于声明组件具备可复用的能力。

declare const Reusable: ClassDecorator;

示例:

// xxx.ets
class MyDataSource implements IDataSource {
  private dataArray: string[] = [];
  private listener: DataChangeListener | undefined;

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): string {
    return this.dataArray[index];
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
  }

  public reloadListener(): void {
    this.listener?.onDataReloaded();
  }

  public registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener;
  }

  public unregisterDataChangeListener(listener: DataChangeListener): void {
    this.listener = undefined;
  }
}

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 0; i < 1000; i++) {
      this.data.pushData(i.toString())
    }
  }

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          ReusableChildComponent({ item: item })
        }
      }, (item: string) => item)
    }
    .width('100%')
    .height('100%')
  }
}

@Reusable
@Component
struct ReusableChildComponent {
  @State item: string = ''

  aboutToReuse(params: ESObject) {
    this.item = params.item;
  }

  build() {
    Row() {
      Text(this.item)
        .fontSize(20)
        .margin({ left: 10 })
    }.margin({ left: 10, right: 10 })
  }
}

相关实例

以下为购物片段示例代码,对比使用组件复用前后,应用侧创建自定义组件的收益以及前后的代码写法对比。

复用前后代码对比

复用前:

LazyForEach(this.GoodDataOne, (item, index) => {
  GridItem() {
    Column() {
      Image(item.img)
        .height(item.hei)
        .width('100%')
        .objectFit(ImageFit.Fill)

      Text(item.introduce)
        .fontSize(14)
        .padding({ left: 5, right: 5 })
        .margin({ top: 5 })
      Row() {
        Row() {
          Text('¥')
            .fontSize(10)
            .fontColor(Color.Red)
            .baselineOffset(-4)
          Text(item.price)
            .fontSize(16)
            .fontColor(Color.Red)
          Text(item.numb)
            .fontSize(10)
            .fontColor(Color.Gray)
            .baselineOffset(-4)
            .margin({ left: 5 })
        }

        Image($r('app.media.photo63'))
          .width(20)
          .height(10)
          .margin({ bottom: -8 })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({ left: 5, right: 5 })
      .margin({ top: 15 })
    }
    .borderRadius(10)
    .backgroundColor(Color.White)
    .clip(true)
    .width('100%')
    .height(290)
  }
}, (item) => JSON.stringify(item))

复用后:

组件被复用后,ArkUI框架会将组件构造对应的参数输入给aboutToReuse生命周期回调,开发者需要在aboutToReuse生命周期中对需要进行更新的状态变量进行赋值,ArkUI框架将会基于最新的状态变量值对UI进行展示。

如果同一种自定义组件的不同实例之间存在较大的结构差异,建议使用reuseId对不同的自定义组件实例分别标注复用组,以达到最佳的复用效果。

如果一个自定义组件中,持有对某个大对象或者其他非必要资源的引用,可以在aboutToRecycle生命周期中释放,以免造成内存泄漏。

LazyForEach(this.GoodDataOne, (item, index) => {
  GridItem() {
    GoodItems({
      boo:item.data.boo,
      img:item.data.img,
      webimg:item.data.webimg,
      hei:item.data.hei,
      introduce:item.data.introduce,
      price:item.data.price,
      numb:item.data.numb,
      index:index
    })
    .reuseId(this.CombineStr(item.type))
  }
}, (item) => JSON.stringify(item))


@Reusable
@Component
struct GoodItems {
  @State img: Resource = $r("app.media.photo61")
  @State webimg?: string = ''
  @State hei: number = 0
  @State introduce: string = ''
  @State price: string = ''
  @State numb: string = ''
  @LocalStorageLink('storageSimpleProp') simpleVarName: string = ''
  boo: boolean = true
  index: number = 0
  controllerVideo: VideoController = new VideoController();

  aboutToReuse(params)
  {
    this.webimg = params.webimg
    this.img = params.img
    this.hei = params.hei
    this.introduce = params.introduce
    this.price = params.price
    this.numb = params.numb
  }

  build() {
    // ...
  }
}

性能收益

通过DevEco Studio的profiler工具分析复用前后的组件创建时间,可以得到应用使能组件复用后的优化情况,组件创建的时间平均从1800us降低到了570us。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开发建议

1.建议复用自定义组件时避免一切可能改变自定义组件的组件树结构和可能使可复用组件中产生重新布局的操作以将组件复用的性能提升到最高。

2.建议列表滑动场景下组件复用能力和LazyForEach渲染控制语法搭配使用以达到性能最优效果。

3.开发者需要区分好自定义组件的创建和更新过程中的行为,并注意到自定义组件的复用本质上是一种特殊的组件更新行为,组件创建过程中的流程与生命周期将不会在组件复用中发生,自定义组件的构造参数将通过aboutToReuse生命周期回调传递给自定义组件。例如,aboutToAppear生命周期和自定义组件的初始化传参将不会在组件复用中发生。

4.避免在aboutToReuse生命周期回调中产生耗时操作,最佳实践是仅在aboutToReuse中做自定义组件更新所需的状态变量值的更新。

5.避免在aboutToReuse中对@Link、@StorageLink、@ObjectLink、@Consume等自动更新值的状态变量进行更新,可能触发不必要的组件刷新。

6.避免使用函数作为复用的自定义组件创建时的入参:

由于在组件复用的场景下,每次复用都需要重新创建组件关联的数据对象,导致重复执行入参中的函数来获取入参结果。如果函数中存在耗时操作,会严重影响性能。正反例如下所示:

【反例】

// 下文中BasicDateSource是实现IDataSource接口的类,具体可参考LazyForEach用法指导
// 此处为复用的自定义组件
@Reusable
@Component
struct ChildComponent {
  @State desc: string = '';
  @State sum: number = 0;

  aboutToReuse(params: Record<string, Object>): void {
    this.desc = params.desc as string;
    this.sum = params.sum as number;
  }

  build() {
    Column() {
      Text('子组件' + this.desc)
        .fontSize(30)
        .fontWeight(30)
      Text('结果' + this.sum)
        .fontSize(30)
        .fontWeight(30)
    }
  }
}

@Entry
@Component
struct Reuse {
  private data: BasicDateSource = new BasicDateSource();

  aboutToAppear(): void {
    for (let index = 0; index < 20; index++) {
      this.data.pushData(index.toString())
    }
  }
    
  // 真实场景的函数中可能存在未知的耗时操作逻辑,此处用循环函数模拟耗时操作
  count(): number {
    let temp: number = 0;
    for (let index = 0; index < 10000; index++) {
      temp += index;
    }
    return temp;
  }

  build() {
    Column() {
      List() {
        LazyForEach(this.data, (item: string) => {
          ListItem() {
            // 此处sum参数是函数获取的,实际开发场景无法预料该函数可能出现的耗时操作,每次进行组件复用都会重复触发此函数的调用
            ChildComponent({ desc: item, sum: this.count() })
          }
          .width('100%')
          .height(100)
        }, (item: string) => item)
      }
    }
  }
}

上述反例操作中,复用的子组件参数sum是通过耗时函数生成。该函数在每次组件复用时都需要执行,会造成性能问题,甚至是列表滑动过程中的卡顿丢帧现象。

【正例】

// 下文中BasicDateSource是实现IDataSource接口的类,具体可参考LazyForEach用法指导
// 此处为复用的自定义组件
@Reusable
@Component
struct ChildComponent {
  @State desc: string = '';
  @State sum: number = 0;

  aboutToReuse(params: Record<string, Object>): void {
    this.desc = params.desc as string;
    this.sum = params.sum as number;
  }

  build() {
    Column() {
      Text('子组件' + this.desc)
        .fontSize(30)
        .fontWeight(30)
      Text('结果' + this.sum)
        .fontSize(30)
        .fontWeight(30)
    }
  }
}

@Entry
@Component
struct Reuse {
  private data: BasicDateSource = new BasicDateSource();
  @State sum: number = 0;

  aboutToAppear(): void {
    for (let index = 0; index < 20; index++) {
      this.data.pushData(index.toString())
    }
    // 执行该异步函数
    this.count();
  }

  // 模拟耗时操作逻辑
  async count() {
    let temp: number = 0;
    for (let index = 0; index < 10000; index++) {
      temp += index;
    }
    // 将结果放入状态变量中
    this.sum = temp;
  }

  build() {
    Column() {
      List() {
        LazyForEach(this.data, (item: string) => {
          ListItem() {
            // 子组件的传参通过状态变量进行
            ChildComponent({ desc: item, sum: this.sum })
          }
          .width('100%')
          .height(100)
        }, (item: string) => item)
      }
    }
  }
}

上述正例操作中,通过耗时函数count生成的结果不变,可以将其放到页面初始渲染时执行一次,将结果赋值给this.sum。在复用组件的参数传递时,通过this.sum来进行。

如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程》以及《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

《鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/562598.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot3 + Vue3 + Element-Plus + TS 实现动态二级菜单级联选择器

SpringBoot3 Vue3 Element-Plus TS 实现动态二级菜单选择器 1、效果展示1.1 点击效果1.2 选择效果1.3 返回值1.4 模拟后端返回数据 2、前端代码2.1 UnusedList.vue2.2 goodsType.ts2.3 http.ts 3、后端代码3.1 GoodsCategoryController.java3.2 GoodsCategoryService.java3.…

读后感-有效沟通

司内的学习已开展8期&#xff0c;内容主要以如何沟通为主&#xff0c;这里将根据个人的学习体会&#xff0c;对所学内容进行梳理与整合&#xff0c;以期更好地吸收和应用所学知识。 沟通是一门技术&#xff0c;其轨迹可循。自来熟的态度&#xff0c;一上来便滔滔不绝地发表言论…

ThingsBoard系统层配置邮件发送

1、前沿 2、案例 1、管理员的身份进行登录 2、选择账户&#xff0c;并将邮箱更改为自己的邮箱&#xff0c;并保存配置 3、退出账号&#xff0c;使用邮箱进行登录&#xff0c;密码还是跟之前一样 4、登录后选择设置-发送邮件 5、登录邮箱申请邮箱的密钥 ​7、 按照图片填…

Mysql The last packet sent successfully to the server was 0 milliseconds ago.

项目启动后&#xff0c;报错&#xff0c;但是我的navicat 数据库连接工具是连接上的&#xff0c;没有问题的&#xff0c;但是程序就是连接不上。端口放开了&#xff0c;防火墙也放开了 先说问题&#xff1a;是网络问题&#xff0c; 如何解决&#xff1a;因为我的机子上又跑了…

计算机网络——数据链路层(介质访问控制)

计算机网络——数据链路层&#xff08;介质访问控制&#xff09; 介质访问控制静态划分信道动态划分信道ALOHA协议纯ALOHA&#xff08;Pure ALOHA&#xff09;原理特点 分槽ALOHA&#xff08;Slotted ALOHA&#xff09;原理特点 CSMA协议工作流程特点 CSMA-CD 协议工作原理主要…

RabbitMQ学习记录

核心概念 Brocker&#xff1a;消息队列服务器实体 Exchange(消息交换机)&#xff1a;它指定消息按什么规则&#xff0c;路由到哪个队列。 Queue(消息队列载体)&#xff1a;每个消息都会被投入到一个或多个队列。 Binding(绑定)&#xff1a;它的作用就是把exchange和queue按…

电磁兼容(EMC):静电放电(ESD)抗扰度试验深度解读(三)

目录 1. 静电抗扰度试验标准试验程序定制的目的 2. 环境条件对充电量的影响 3. 环境级别与空气和接触放电的关系 4. 试验等级的选择 1. 静电抗扰度试验标准试验程序定制的目的 保护设备免受静电放电影响的问题对制造厂和用户来说都是相当重要的。 随着微电子元件的广泛应用…

gradle安装和部署

准备工作 下载地址&#xff1a;https://gradle.org/releases/ 安装和配置环境变量 将压缩包解压到/usr/local/目录下 unzip gradle-8.7-bin.zip -d /usr/local/找到gradle的安装目录/usr/local/gradle-8.7 编辑/etc/vi /etc/profileprofile配置环境变量&#xff08;这是ce…

《强势》如何在工作、恋爱和人际交往中快速取得主导权? - 三余书屋 3ysw.net

强势&#xff1a;如何在工作、恋爱和人际交往中快速取得主导权&#xff1f; 大家好&#xff0c;今天我们要解读的是一本名为《强势》的书籍。我将花费大约20分钟的时间&#xff0c;为您详细讲解这本书的精华内容&#xff0c;包括如何在家庭关系、职场关系和朋友关系中迅速取得…

Flowable 基本用法

一. 什么是Flowable Flowable 是一个基于 Java 的开源工作流引擎&#xff0c;用于实现和管理业务流程。它提供了强大的工作流引擎和一套丰富的工具&#xff0c;使开发人员能够轻松地建模、部署、执行和监控各种类型的业务流程。Flowable 是 Activiti 工作流引擎的一个分支&am…

项目7-音乐播放器4+喜欢/收藏音乐

1.喜欢/收藏音乐模块设计 1.1 请求响应模块设计 请求&#xff1a; { post, /lovemusic/likeMusic data: id//音乐id } 响应&#xff1a; { "status": 0, "message": "点赞音乐成功", "da…

环境监测系统--------MQ系列气体检测模块驱动教程(保姆级教程)

⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩在环境检测中我们经常会用到检测气体的传感器&#xff0c;检测乙醇、甲烷、一氧化碳、氢气等等&#xff0c;博主呕心沥血对MQ系列传感器做一个史上最详细的使用教程…

UML类图详解

UML类图结构解析 UML类图是一种结构图&#xff0c;用于描述系统的静态结构。它主要用于展示系统中的类&#xff08;class&#xff09;、接口&#xff08;interface&#xff09;、协作&#xff08;collaboration&#xff09;、数据类型&#xff08;data type&#xff09;等以及…

CH341A/B USB转USART/I2C/SPI介绍

CH341A/B USB转USART/I2C/SPI介绍 &#x1f4cd;CH341官方文档&#xff1a;https://www.wch.cn/downloads/CH341DS2_PDF.html CH341A/B是一个USB总线的转接芯片&#xff0c;通过USB总线提供异步串口、打印口、并口以及常用的2线和4线等同步串行接口。 &#x1f341;芯片封装&a…

翻译 《The Old New Thing》 - Returning values from a dialog procedure

Returning values from a dialog procedure - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20031107-00/?p41923 Raymond Chen 2003年11月7日 简要 这篇文章由Raymond Chen撰写&#xff0c;解释了对话框过程如何从Windows编程中返回值。他…

如何实现在 Windows 上运行 Linux 程序?

在Windows 上运行Linux程序是可以通过以下几种方法实现: 1.使用 Windows Subsystem for Linux (WSL): WSL是微软提供的功能&#xff0c;可以在Windows 10上运行一个完整的Linux系统。用户可以在Microsoft Store中安装所需的 在开始前我有一些资料&#xff0c;是我根据网友给的…

2024团体程序设计天梯赛L1-103 整数的持续性

题目链接L1-103 整数的持续性 #include<iostream> #include<stdio.h> #include<algorithm> using namespace std; struct node{int x;int d; }p[2000]; bool cmp(node a, node b) {if (a.d b.d) return a.x < b.x;return a.d>b.d; } int cnt, cntt; v…

密钥密码学(三)

原文&#xff1a;annas-archive.org/md5/b5abcf9a07e32fc6f42b907f001224a1 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十六章&#xff1a;三次通行协议 本章内容包括 基于指数的三次通行协议 基于矩阵乘法的三次通行协议 基于双边矩阵乘法的三次通行协议 …

UI5 快速入门教程

环境准备 node >16.8 ,VSCode&#xff0c;官方网址 开始 创建一个根文件夹&#xff0c;根文件中创建一个package.json文件 {"name": "quickstart-tutorial","private": true,"version": "1.0.0","author":…

Java基于微信小程序的讲座预约系统的研究与实现,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…
最新文章