鸿蒙HarmonyOS NEXT开发:优化用户界面性能——组件复用(@Reusable装饰器)

news2025/2/13 14:34:42

文章目录

      • 一、概述
      • 二、原理介绍
      • 三、使用规则
      • 四、复用类型详解
        • 1、标准型
        • 2、有限变化型
          • 2.1、类型1和类型2布局不同,业务逻辑不同
          • 2.2、类型1和类型2布局不同,但是很多业务逻辑公用
        • 3、组合型
        • 4、全局型
        • 5、嵌套型

一、概述

组件复用是优化用户界面性能,提升应用流畅度的一种重要手段,通过复用已存在的组件节点而非创建新的节点,从而确保UI线程的流畅性与响应速度。

组件复用针对的是自定义组件,只要发生了相同自定义组件销毁和再创建的场景,都可以使用组件复用,例如滑动列表场景,会出现大量重复布局的创建,使用组件复用可以大幅度降低了因频繁创建与销毁组件带来的性能损耗。

然而,面对复杂的业务场景或者布局嵌套的场景下,组件复用使用不当,可能会导致复用失效或者性能提升不能最大化。例如列表中存在多种布局形态的列表项,无法直接复用。

本文基于对常见的布局类型进行划分,通过合理使用组件复用方式,帮助开发者更好的理解和实施组件复用策略以优化应用性能。

二、原理介绍

组件复用机制如下:

  • 标记为@Reusable的组件从组件树上被移除时,组件和其对应的JSView对象都会被放入复用缓存中。
  • 当列表滑动新的ListItem将要被显示,List组件树上需要新建节点时,将会从复用缓存中查找可复用的组件节点。
  • 找到可复用节点并对其进行更新后添加到组件树中。从而节省了组件节点和JSView对象的创建时间。

组件复用原理图

在这里插入图片描述

1、@Reusable表示组件可以被复用,结合LazyForEach懒加载一起使用,可以进一步解决列表滑动场景的瓶颈问题,提供滑动场景下高性能创建组件的方式来提升滑动帧率。

2、CustomNode是一种自定义的虚拟节点,它可以用来缓存列表中的某些内容,以提高性能和减少不必要的渲染。通过使用CustomNode,可以实现只渲染当前可见区域内的数据项,将未显示的数据项缓存起来,从而减少渲染的数量,提高性能。

3、RecycleManager是一种用于优化资源利用的回收管理器。当一个数据项滚出屏幕时,不会立即销毁对应的视图对象,而是将该视图对象放入复用池中。当新的数据项需要在屏幕上展示时,RecycleManager会从复用池中取出一个已经存在的视图对象,并将新的数据绑定到该视图上,从而避免频繁的创建和销毁过程。通过使用RecycleManager,可以大大减少创建和销毁视图的次数,提高列表的滚动流畅度和性能表现。

4、CachedRecycleNodes是CustomNode的一个集合,常是用于存储被回收的CustomNode对象,以便在需要时进行复用。

说明
需要注意的是,虽然这里是使用List组件进行举例,但是不代表组件复用只能用在滚动容器里,只要是发生了相同自定义组件销毁和再创建的场景,都可以使用组件复用。

三、使用规则

组件复用的示例代码如下:

// xxx.ets
export class Message {
  value: string | undefined;

  constructor(value: string) {
    this.value = value
  }
}

@Entry
@Component
struct Index {
  @State switch: boolean = true
  build() {
    Column() {
      Button('Hello World')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.switch = !this.switch
        })
      if (this.switch) {
        Child({ message: new Message('Child') })
          // 如果只有一个复用的组件,可以不用设置reuseId
          .reuseId('Child')
      }
    }
    .height("100%")
    .width('100%')
  }
}

@Reusable
@Component
struct Child {
  @State message: Message = new Message('AboutToReuse');

  aboutToReuse(params: Record<string, ESObject>) {
    console.info("Recycle Child")
    this.message = params.message as Message
  }

  build() {
    Column() {
      Text(this.message.value)
        .fontSize(20)
    }
    .borderWidth(2)
    .height(100)
  }
}

1.@Reusable:自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力。

2.aboutToReuse:当一个可复用的自定义组件从复用缓存中重新加入到节点树时,触发aboutToReuse生命周期回调,并将组件的构造参数传递给aboutToReuse。

3.reuseId:用于标记自定义组件复用组,当组件回收复用时,复用框架将根据组件的reuseId来划分组件的复用组。如果只有一个复用的组件,可以不用设置reuseId。

四、复用类型详解

组件复用基于不同的布局效果和复用的诉求,可以分为以下五种类型。

表1 组件复用类型说明

复用类型描述复用思路
标准型复用组件之间布局完全相同标准复用
有限变化型复用组件之间布局有所不同,但是类型有限使用reuseId或者独立成不同自定义组件
组合型复用组件之间布局有不同,情况非常多,但是拥有共同的子组件将复用组件改为@Builder,让内部子组件相互之间复用
全局型组件可在不同的父组件中复用,并且不适合使用@Builder使用BuilderNode自定义复用组件池,在整个应用中自由流转
嵌套型复用组件的子组件的子组件存在差异采用化归思想将嵌套问题转化为上面四种标准类型来解决

下面将以滑动列表的场景为例介绍5种复用类型的使用场景,为了方便描述,下文将需要复用的自定义组件如ListItem的内容组件,叫做复用组件,将其下层的自定义组件叫做子组件、复用组件上层的自定义组件叫做父组件。为了更直观,下面每一种复用类型都会通过简易的图形展示组件的布局方式,并且为了便于分辨,布局相同的子组件使用同一种形状图形表示。

1、标准型

在这里插入图片描述

这是一个标准的组件复用场景,一个滚动容器内的复用组件布局相同,只有数据不同,这种类型的组件复用可以直接参考资料组件复用。其缓存池如下,因为该场景只有一个复用组件,所以在缓存中只有一个复用组件list:

在这里插入图片描述

典型场景如下,列表Item布局基本完全相同。

在这里插入图片描述

标准型组件复用的示例代码如下:

@Entry
@Component
struct ReuseType1 {
  // ...
  build() {
    Column() {
      List() {
        LazyForEach(this.dataSource, (item: string) => {
          ListItem() {
            CardView({ item: item })
          }
        }, (item: string) => item)
      }
    }
  }
}

// 复用组件
@Reusable
@Component
export struct CardView {
  @State item: string = '';

  aboutToReuse(params: Record<string, Object>): void {
    this.item = params.item as string;
  }
  // ...
}
2、有限变化型

在这里插入图片描述

如上图所示,有限变化型指的是父组件内存在多个类型的复用单元,这些类型的单元布局有所不同,根据业务逻辑的差异可以分为以下两种情况:

  • 类型1和类型2布局不同,业务逻辑不同:这种情况可以使用两个不同的自定义组件进行复用。

  • 类型1和类型2布局不同,但是很多业务逻辑公用:这种情况为了复用公用的逻辑代码,减少代码冗余,可以给同一个组件设置不同的reuseId来进行复用。

下面将分别介绍这两种场景下的组件复用方法。

2.1、类型1和类型2布局不同,业务逻辑不同

在这里插入图片描述

类型1和类型2布局不同,业务逻辑不同:因为两种类型的组件布局会对应应用不同的业务处理逻辑,建议将两种类型的组件分别使用两个不同的自定义组件,分别进行复用。给复用组件1和复用组件2设置不同的reuseId,此时组件复用池内的状态如下图所示,复用组件1和复用组件2处于不同的复用list中。

例如下面的列表场景,列表项布局差距比较大,有多图片的列表项,有单图片的列表项:

在这里插入图片描述

实现方式可参考以下示例代码:

@Entry
@Component
struct ReuseType2A {
  // ...

  build() {
    Column() {
      List() {
        LazyForEach(this.dataSource, (item: number) => {
          ListItem() {
            if (item % 2 === 0) { // 模拟业务条件判断
              SinglePicture({ item: item }) // 渲染单图片列表项
            } else {
              MultiPicture({ item: item }) // 渲染多图片列表项
            }
          }
        }, (item: number) => item + '')
      }
    }
  }
}

// 复用组件1
@Reusable
@Component
struct SinglePicture {
  // ...
}

// 复用组件2
@Reusable
@Component
struct MultiPicture {
  // ...
}
2.2、类型1和类型2布局不同,但是很多业务逻辑公用

在这里插入图片描述

类型1和类型2布局不同,但是很多业务逻辑公用:在这种情况下,如果将组件分为两个自定义组件进行复用,会存在代码冗余问题。根据布局的差异,可以给同一个组件设置不同的reuseId从而复用同一个组件,达到逻辑代码的复用。

根据组件复用原理与使用可知,复用组件是依据reuseId来区分复用缓存池的,而自定义组件的名称就是默认的reuseId。因此,为复用组件显式设置两个不同的reuseId与使用两个自定义组件进行复用,对于 ArkUI 而言,复用逻辑完全相同,复用池也一样,只不过复用池中复用组件的list以reuseId作为标识。

例如下面这个场景,布局差异比较小,业务逻辑一样都是跳转到页面详情。这种情况复用同一个组件,只需要使用if/else条件语句来控制布局的结构,就可以实现,同时可以复用跳转详情的公用逻辑代码。但是这样会导致在不同逻辑会反复去修改布局,造成性能损耗。开发者可以根据不同的条件,设置不同的reuseId来标识需要复用的组件,省去重复执行if的删除重创逻辑,提高组件复用的效率和性能。

在这里插入图片描述

实现方式可以参考以下示例:

@Entry
@Component
struct ReuseType2B {
  // ...

  build() {
    Column() {
      List() {
        LazyForEach(this.dataSource, (item: MemoInfo) => {
          ListItem() {
            MemoItem({ memoItem: item })// 使用reuseId进行组件复用的控制
              .reuseId((item.imageSrc !== '') ? 'withImage' : 'noImage')
          }
        }, (item: MemoInfo) => JSON.stringify(item))
      }
    }
  }
}

@Reusable
@Component
export default struct MemoItem {
  @State memoItem: MemoInfo = MEMO_DATA[0];

  aboutToReuse(params: Record<string, Object>) {
    this.memoItem = params.memoItem as MemoInfo;
  }

  build() {
    Row() {
      // ...
      if (this.memoItem.imageSrc !== '') {
        Image($r(this.memoItem.imageSrc))
          .width(90)
          .aspectRatio(1)
          .borderRadius(10)
      }
    }
    // ...
  }
}
3、组合型

在这里插入图片描述

这种类型中复用组件之间存在不同,并且情况比较多,但拥有共同的子组件。如果使用有限变化型的组件复用方式,将所有类型的复用组件写成自定义组件分别复用,不同复用组件(组件名不同或者reuseld不同)之间相同子组件无法复用,因为它们在缓存池的不同List中。

对此可以将复用组件转变为@Builder函数,使复用组件内部共同的子组件的缓存池在父组件上共享,此时组件复用池内的状态如下图所示。

典型场景如下图,这个列表的Item有多种组合方式。但是每个Item上面和下面的布局是一样的,中间部分的布局有所不同,有单一图片、视频、九宫等等。

在这里插入图片描述

示例代码如下,列举了单一图片、视频和九宫格图片三种类型的列表项目,使用Builder函数后将子组件组合成三种不同的类型,使内部共同的子组件就处于同一个父组件FriendsMomentsPage下。对这些子组件使用组件复用时,他们的缓存池也会在父组件上共享,节省组件创建时的消耗。

@Entry
@Component
struct ReuseType3 {
  // ...

  @Builder
  itemBuilderSingleImage(item: FriendMoment) { // 单大图列表项
    // ...
  }

  @Builder
  itemBuilderGrid(item: FriendMoment) { // 九宫格列表项
    // ...
  }

  @Builder
  itemBuilderVideo(item: FriendMoment) { // 视频列表项
    // ...
  }

  build() {
    Column() {
      List() {
        LazyForEach(this.momentDataSource, (item: FriendMoment) => {
          ListItem() {
            if (item.type === 1) { // 根据不同类型,使用不同的组合
              this.itemBuilderSingleImage(item);
            } else if (item.type === 2) {
              this.itemBuilderGrid(item);
            } else if (item.type === 3) {
              this.itemBuilderVideo(item);
            } else {
              // ...
            }
          }
        }, (moment: FriendMoment) => JSON.stringify(moment))
      }
    }
  }
}

@Reusable
@Component
struct ItemTop {
  // ...
}

@Reusable
@Component
struct ItemBottom {
  // ...
}

@Reusable
@Component
struct MiddleSingleImage {
  // ...
}

@Reusable
@Component
struct MiddleGrid {
  // ...
}

@Reusable
@Component
struct MiddleVideo {
  // ...
}
4、全局型

在这里插入图片描述

默认的组件复用行为,是将子组件放在父组件的缓存池里,受到这个限制,不同父组件中的相同子组件无法复用,推荐的解决方案是将父组件改为builder函数,让子组件共享组件复用池,但是由于在一些应用场景下,父组件承载了复杂的带状态的业务逻辑,而builder是无状态的,修改会导致难以维护,因此开发者可以使用BuilderNode自行管理组件复用池。

有时候应用在多个tab页之间切换,tab页之间结构类似,需要在tab页之间复用组件,提升页面切换性能。或者有些应用在组合型场景下,由于复用组件内部含有较多带状态的业务逻辑,所以不适合改为Builder函数。

针对这种类型的组件复用场景,可以通过BuilderNode自定义缓存池,将要复用的组件封装在BuilderNode中,将BuilderNode的NodeController作为复用的最小单元,自行管理复用池。

5、嵌套型

在这里插入图片描述

嵌套型是指复用组件的子组件的子组件之间存在差异的复用场景。如上图所示,列表项复用组件1之间的差异是子组件B的子组件不一样,有子组件C、D、E三种。这种情况可以运行化归的思想,将复杂的问题转化为已知的、简单的问题

嵌套型实际上是上面四种类型的组合,以上图为例,可以通过有限变化型的方案,将子组件B变为子组件B1/B2/B3,这样问题就变成了一个标准的有限变化型,A/B1/C、A/B2/D、A/B3/E会分别作为一个组合进行复用,复用池如下:
在这里插入图片描述

下面列举一个简单的示例介绍嵌套型的使用:

@Entry
@Component
struct ReuseType5A {
  // ...
  build() {
    Column() {
      List() {
        LazyForEach(this.dataSource, (item: number) => {
          ListItem() {
            if (item % 2 === 0) { // 模拟类型一的条件
              ReusableComponent({ item: item })
                .reuseId('type1')
            } else if (item % 3 === 0) { // 模拟类型二的条件
              ReusableComponent({ item: item })
                .reuseId('type2')
            } else { // 模拟类型三的条件
              ReusableComponent({ item: item })
                .reuseId('type3')
            }
          }
        }, (item: number) => item.toString())
      }
    }
  }
}

// 复用组件
@Reusable
@Component
struct ReusableComponent {
  @State item: number = 0;

  build() {
    Column() {
      ComponentA()
      if (this.item % 2 === 0) {
        ComponentB1()
      } else if (this.item % 3 === 0) {
        ComponentB2()
      } else {
        ComponentB3()
      }
    }
  }
}

@Component
struct ComponentA {
  // ...
}

@Component
struct ComponentB1 {
  build() {
    Column() {
      ComponentC()
    }
  }
}

@Component
struct ComponentB2 {
  build() {
    Column() {
      ComponentD()
    }
  }
}

@Component
struct ComponentB3 {
  build() {
    Column() {
      ComponentE()
    }
  }
}

@Component
struct ComponentC {
  // ...
}

@Component
struct ComponentD {
  // ...
}

@Component
struct ComponentE {
  // ...
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows中使用Docker安装Anythingllm,基于deepseek构建自己的本地知识库问答大模型,可局域网内多用户访问、离线运行

文章目录 Windows中使用Docker安装Anythingllm&#xff0c;基于deepseek构建自己的知识库问答大模型1. 安装 Docker Desktop2. 使用Docker拉取Anythingllm镜像2. 设置 STORAGE_LOCATION 路径3. 创建存储目录和 .env 文件.env 文件的作用关键配置项 4. 运行 Docker 命令docker r…

[SAP ABAP] OO ALV报表练习1

销售订单明细查询报表 业务目的&#xff1a;根据选择屏幕的筛选条件&#xff0c;使用 ALV 报表&#xff0c;显示销售订单详情 效果展示 用户的输入条件界面 用户的查询结果界面 涉及的主要功能点&#xff1a; 1.当在销售订单明细查询页面取不到任何数据时&#xff0c;在选择…

数据库高安全—数据保护:数据动态脱敏

书接上文数据库高安全—审计追踪&#xff1a;传统审计&统一审计&#xff0c;从传统审计和统一审计两方面对高斯数据库的审计追踪技术进行解读&#xff0c;本篇将从数据动态脱敏方面对高斯数据库的数据保护技术进行解读。 5.1 数据动态脱敏 数据脱敏&#xff0c;顾名思义就…

Datawhale 数学建模导论二 2025年2月

第6章 数据处理与拟合模型 本章主要涉及到的知识点有&#xff1a; 数据与大数据Python数据预处理常见的统计分析模型随机过程与随机模拟数据可视化 本章内容涉及到基础的概率论与数理统计理论&#xff0c;如果对这部分内容不熟悉&#xff0c;可以参考相关概率论与数理统计的…

记录 | WPF基础学习MVVM例子讲解1

目录 前言一、NotificationObject与数据属性创建个类&#xff0c;声明NotificationObject 二、DelegateCommand与命令属性三、View与ViewModel的交互&#xff08;难点&#xff09;在ViewModel文件下创建MainWindowViewModel数据和方法绑定资源指定 代码下载四、优势体现代码下载…

PyTorch 中 `torch.cuda.amp` 相关警告的解决方法

在最近的写代码过程中&#xff0c;遇到了两个与 PyTorch 的混合精度训练相关的警告信息。这里随手记录一下。 警告内容 警告 1: torch.cuda.amp.autocast FutureWarning: torch.cuda.amp.autocast(args...) is deprecated. Please use torch.amp.autocast(cuda, args...) i…

实验7 路由器之间IPsec VPN配置

实验7 路由器之间IPsec VPN配置 1.实验目的 通过在两台路由器之间配置IPsec VPN连接&#xff0c;掌握IPsec VPN配置方法&#xff0c;加深对IPsec协议的理解。 2.实验内容 &#xff08;1&#xff09;按照实验拓扑搭建实验环境。 &#xff08;2&#xff09;在路由器R1和R4配置IP…

小白零基础如何搭建CNN

1.卷积层 在PyTorch中针对卷积操作的对象和使用的场景不同&#xff0c;如有1维卷积、2维卷积、 3维卷积与转置卷积&#xff08;可以简单理解为卷积操作的逆操作&#xff09;&#xff0c;但它们的使用方法比较相似&#xff0c;都可以从torch.nn模块中调用&#xff0c;需要调用的…

【Java八股文】01-Java基础面试篇

【Java八股文】01-Java基础面试篇 概念Java特点Java为什么跨平台JVM、JDK、JRE关系 面向对象什么是面向对象&#xff0c;什么是封装继承多态&#xff1f;多态体现的方面面向对象设计原则重载重写的区别抽象类和实体类区别Java抽象类和接口的区别抽象类可以被实例化吗 深拷贝浅拷…

k8s部署logstash

1. 编写logstash.yaml配置文件 --- apiVersion: v1 kind: Service metadata:name: logstash spec:type: ClusterIPclusterIP: Noneports:- name: logstash-tcpport: 5000targetPort: 5000- name: logstash-beatsport: 5044targetPort: 5044- name: logstash-apiport: 9600targ…

Arcgis/GeoScene API for JavaScript 三维场景底图网格设为透明

项目场景&#xff1a; 有时候加载的地图服务白色区域会露底&#xff0c;导致在三维场景时&#xff0c;露出了三维网格&#xff0c;影响效果&#xff0c;自此&#xff0c;我们需要将三维场景的底图设为白色或透明。 问题描述 如图所示&#xff1a; 解决方案&#xff1a; 提示…

《qt open3d网格拉普拉斯平滑》

qt open3d网格拉普拉斯平滑 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionFilterLaplacian_triggered();void MainWindow::on_actionFil

怎么选择免费的SEO排名工具

随着2025年互联网的迅猛发展&#xff0c;越来越多的企业意识到&#xff0c;拥有一个高排名的网站对于品牌曝光和吸引客户至关重要。尤其是通过SEO&#xff08;搜索引擎优化&#xff09;&#xff0c;可以提高网站在搜索引擎中的排名&#xff0c;进而带来更多的自然流量&#xff…

SSH隧道+Nginx:绿色通道详解(SSH Tunnel+nginx: Green Channel Detailed Explanation)

SSH隧道Nginx&#xff1a;内网资源访问的绿色通道 问题背景 模拟生产环境&#xff0c;使用两层Nginx做反向代理&#xff0c;请求公网IP来访问内网服务器的网站。通过ssh隧道反向代理来实现&#xff0c;重点分析一下nginx反代的基础配置。 实验环境 1、启动内网服务器的tomca…

Spring 项目接入 DeepSeek,分享两种超简单的方式!

⭐自荐一个非常不错的开源 Java 面试指南&#xff1a;JavaGuide &#xff08;Github 收获148k Star&#xff09;。这是我在大三开始准备秋招面试的时候创建的&#xff0c;目前已经持续维护 6 年多了&#xff0c;累计提交了 5600 commit &#xff0c;共有 550 多位贡献者共同参与…

半小时在本地部署DeepSeek的Janus Pro,进行图片分析和文生图

半小时在本地部署DeepSeek的Janus Pro&#xff0c;进行图片分析和文生图 下载Janus Pro源代码下载模型文件创建Python虚拟环境安装依赖包Janus Pro测试运行程序图片分析测试文生图测试使用中文提示词使用英文提示词 测试印象&#xff1a; 整体模型体积较小&#xff0c;个人可以…

急停信号的含义

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#的运动控制程序的时候&#xff0c;一个必要的步骤就是确认设备按钮的急停…

设置mysql的主从复制模式

mysql设置主从复制模式似乎很容易&#xff0c;关键在于1&#xff09;主库启用二进制日志&#xff0c;2&#xff09;从库将主库设为主库。另外&#xff0c;主从复制&#xff0c;复制些什么&#xff1f;从我现在获得的还很少的经验来看&#xff0c;复制的内容有表&#xff0c;用户…

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测(Maltab)

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 完整代码私信回复三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 一、引言 1、研究背景和意义 在现代数据科学领域&#xff0c;时间序列…

ArcGIS基础知识之ArcMap基础设置——ArcMap选项:常规选项卡设置及作用

作为一名 GIS 从业者,ArcMap 是我们日常工作中不可或缺的工具。对于初学者来说,掌握 ArcMap 的基础设置是迈向 GIS 分析与制图的第一步。今天,就让我们一起深入了解 ArcMap 选项中常规选项卡的各个设置,帮助大家更好地使用这款强大的软件。 在 ArcMap 中,常规选项卡是用户…