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

文章目录

      • 一、概述
      • 二、原理介绍
      • 三、使用规则
      • 四、复用类型详解
        • 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 = truebuild() {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 {// ...@BuilderitemBuilderSingleImage(item: FriendMoment) { // 单大图列表项// ...}@BuilderitemBuilderGrid(item: FriendMoment) { // 九宫格列表项// ...}@BuilderitemBuilderVideo(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.rhkb.cn/news/16810.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pyrender 渲染报错解决

pyrender渲染后&#xff0c;出来的图样子不对&#xff1a; 正确的图&#xff1a; 解决方法&#xff1a; pip install numpy1.26 下面的不是必须的&#xff1a; pip install pyrender0.1.45 os.environ["PYOPENGL_PLATFORM"] "egl" os.environ[EGL_DEVI…

CCFCSP第34次认证第一题——矩阵重塑(其一)

第34次认证第一题——矩阵重塑&#xff08;其一&#xff09; 官网链接 时间限制&#xff1a; 1.0 秒 空间限制&#xff1a; 512 MiB 相关文件&#xff1a; 题目目录&#xff08;样例文件&#xff09; 题目背景 矩阵&#xff08;二维&#xff09;的重塑&#xff08;reshap…

Neurlps2024论文解读|BERTs are Generative In-Context Learners-water-merged

论文标题 BERTs are Generative In-Context Learners BERTs 是生成式上下文学习器 论文链接 BERTs are Generative In-Context Learners论文下载 论文作者 David Samuel 内容简介 本文探讨了掩码语言模型&#xff08;如DeBERTa&#xff09;在上下文学习中的生成能力&…

深入理解Java对接DeepSeek

其实&#xff0c;整个对接过程很简单&#xff0c;就四步&#xff0c;获取key&#xff0c;找到接口文档&#xff0c;接口测试&#xff0c;代码对接。 1.获取 KEY https://platform.deepseek.com/transactions 直接付款就是了&#xff08;现在官网暂停充值2025年2月7日&#xf…

OSPF高级特性(3):安全特效

引言 OSPF的基础我们已经结束学习了&#xff0c;接下来我们继续学习OSPF的高级特性。为了方便大家阅读&#xff0c;我会将高级特性的几篇链接放在末尾&#xff0c;所有链接都是站内的&#xff0c;大家点击即可阅读&#xff1a; OSPF基础&#xff08;1&#xff09;&#xff1a;工…

HCIA项目实践--静态路由的总结和简单配置

七、静态路由 7.1 路由器获取未知网段的路由信息&#xff1a; &#xff08;1&#xff09;静态路由&#xff1a;网络管理员手工配置的路由条目&#xff0c;它不依赖网络拓扑的变化进行自动更新&#xff0c;而是根据管理员预先设定的路径来转发数据包。其优点是配置简单、占用系…

3dtiles——Cesium ion for Autodesk Revit Add-In插件

一、说明&#xff1a; Cesium已经支持3dtiles的模型格式转换&#xff1b; 可以从Cesium官方Aesset中上传gltf等格式文件转换为3dtiles&#xff1b; 也可以下载插件&#xff08;例如revit-cesium插件&#xff09;转换并自动上传到Cesium官方Aseet中。 Revit转3dtiles插件使用…

HCIA项目实践---网络层次常见的三种模型

2.2 网络的层次 2.2.1 常见的三种网络层次划分 应用层 &#xff08;1&#xff09;OSI 七层模型 物理层&#xff1a;处于最底层&#xff0c;主要负责处理物理介质上的信号传输&#xff0c;如电缆、光纤、无线等。其作用是定义物理设备的接口标准、信号的编码方式、传输速率等&…

【图片转换PDF】多个文件夹里图片逐个批量转换成多个pdf软件,子文件夹单独合并转换,子文件夹单独批量转换,基于Py的解决方案

建筑设计公司在项目执行过程中&#xff0c;会产生大量的设计图纸、效果图、实景照片等图片资料。这些资料按照项目名称、阶段、专业等维度存放在多个文件夹和子文件夹中。 操作需求&#xff1a;为了方便内部管理和向客户交付完整的设计方案&#xff0c;公司需要将每个项目文件…

Python:凯撒密码

题目内容&#xff1a; 凯撒密码是古罗马恺撒大帝用来对军事情报进行加密的算法&#xff0c;它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列该字符后面第三个字符&#xff0c;对应关系如下&#xff1a; 原文&#xff1a;A B C D E F G H I J K L M N O P Q R …

亚信安全正式接入DeepSeek

亚信安全致力于“数据驱动、AI原生”战略&#xff0c;早在2024年5月&#xff0c;推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体&#xff0c;为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来&#xff0c;亚信安全人工智能实验室利…

2024BaseCTF_week4_web上

继续&#xff01;冲冲冲 目录 圣钥之战1.0 nodejs 原型 原型链 原型链污染 回到题目 flag直接读取不就行了&#xff1f; 圣钥之战1.0 from flask import Flask,request import jsonapp Flask(__name__)def merge(src, dst):for k, v in src.items():if hasattr(dst, __geti…

【Java 面试 八股文】Redis篇

Redis 1. 什么是缓存穿透&#xff1f;怎么解决&#xff1f;2. 你能介绍一下布隆过滤器吗&#xff1f;3. 什么是缓存击穿&#xff1f;怎么解决&#xff1f;4. 什么是缓存雪崩&#xff1f;怎么解决&#xff1f;5. redis做为缓存&#xff0c;mysql的数据如何与redis进行同步呢&…

使用 Dockerfile 构建自定义 Nginx 镜像并集成 nginx_upstream_check_module

目录 1. 为什么需要自定义 Nginx 镜像&#xff1f; 2. Dockerfile 解析 2.1 基础镜像选择 2.2 安装依赖 2.3 下载并解压 Nginx 源码 2.4 应用补丁并编译 Nginx 2.5 暴露端口并设置启动命令 3. 构建并运行自定义 Nginx 镜像 3.1 构建镜像 3.2 运行容器 3.3 健康检测配…

Python办公自动化之PDF

python版本&#xff1a;3.13.1 开发工具&#xff1a;pycharm 安装三方库&#xff1a;pypdf2 、pdfplumber、pymupdf 一、从PDF中提取文字 用Python从PDF中提取文字-CSDN博客 二、从PDF中提取表格 用Python从PDF中提取表格-CSDN博客 三、拆分和合并PDF文件 用Python拆…

ds-download-link 插件:以独特图标选择,打造文章下载链接

源码介绍 “ds-download-link”插件为 WordPress 网站提供了在文章编辑器中添加下载链接的功能&#xff0c;每个下载链接都支持图标选择&#xff0c;并能将这些链接以美观的样式展示在文章前端页面。以下是该插件的主要特性和功能&#xff1a; 后台功能 在文章编辑器下方添加…

实操部署DeepSeek,添加私有知识库

目录 一、环境介绍 PowerShell版本&#xff1a; wsl版本&#xff1a; 虚拟机版本&#xff1a; 本机IP&#xff1a; 虚拟机IP&#xff1a; 容器宿主机IP&#xff08;host.docker.internal&#xff09;&#xff1a; Docker版本&#xff1a; Docker Compose版本&#xff…

一致性Hash算法延伸至Redis分片扩容使Lua脚本失效如何解决

文章部分内容来源&#xff1a;小林coding 问题场景&#xff1a;我们需要用Lua脚本&#xff0c;并且这个Lua脚本需要用到两个Key&#xff0c;但这两个Key必须命中同一台机器才可以&#xff0c;不然Lua脚本就会执行失败。如果集群扩容可能会导致两个Key落到不同的节点上导致Lua脚…

macMini16G内存M4芯片 DeepSeek-r1本地化部署+chatbox三步走

DeepSeek本地化部署&#xff0c;有利于保护隐私&#xff0c;调用也方便。 大体来说分为3步&#xff1a;安装ollama&#xff0c;获取deepseekR1模型&#xff0c;chatbox设置并调用。 1.下载ollama客户端&#xff0c;并安装。 https://ollama.com/download 2.获取deepseekR1模型…

8.flask+websocket

http是短连接&#xff0c;无状态的。 websocket是长连接&#xff0c;有状态的。 flask中使用websocket from flask import Flask, request import asyncio import json import time import websockets from threading import Thread from urllib.parse import urlparse, pars…