鸿蒙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.mzph.cn/news/895409.shtml

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

相关文章

【AI大模型】Ollama部署本地大模型DeepSeek-R1,交互界面Open-WebUI,RagFlow构建私有知识库

文章目录 DeepSeek介绍公司背景核心技术产品与服务应用场景优势与特点访问与体验各个DeepSeek-R系列模型的硬件需求和适用场景 Ollama主要特点优势应用场景安装和使用配置环境变量总结 安装open-webui下载和安装docker desktop配置镜像源安装open-webui运行和使用 RagFlow介绍主…

更加通用的Hexo多端部署原理及实现,适用于各种系统之间

本文推荐在作者的个人博客网站阅读&#xff1a;shenying.online 一、故事背景 故事发生在大学上学期间&#xff08;而不是寒假&#xff09;。上学期间&#xff0c;宿舍条件极其恶劣&#xff0c;半夜断电、空间狭小。我们大学垃圾条件使用游戏本的种种弊端被无限放大&#xff1…

开源、免费项目管理工具比较:2025最新整理30款

好用的开源、免费版项目管理系统有&#xff1a;1.Redmine&#xff1b;2. Taiga&#xff1b;3. OpenProject&#xff1b; 4.ProjectLibre&#xff1b; 5.GanttProject&#xff1b; 6.Tuleap&#xff1b; 7.Trac&#xff1b;8. Phabricator&#xff1b; 9.Notion&#xff1b; 10.…

组织结构改革:激活企业活力的 “源头活水”

难以适应市场变化、内部沟通与协作不畅、决策效率低下、运营成本增加、人才流失严重、员工士气下降、战略目标难以实现……企业如何根据市场环境变化和自身发展需求&#xff0c;灵活调整组织框架&#xff0c;赋能企业的持续健康发展&#xff1f; 某国有投资建设集团旗下的二级…

Mac之JDK安装

Mac之JDK安装 一.安装 jdk 打开终端输入命令:java -version 查看是否已安装 JDK Oracle 官方下载地址 根据自己Mac 系统安装 查看 Mac 系统&#xff0c;打开中断命令&#xff0c;输入: uname -a Compressed Archive 是压缩文档&#xff0c;下载的是一个 .tar.gz 压缩包 D…

【含文档+PPT+源码】基于Python的全国景区数据分析以及可视化实现

项目介绍 本课程演示的是一款基于Python的全国景区数据分析以及可视化实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 带你从零开始部署运行本套系统 该…

SQL注入之布尔和时间盲注,sqli-labs

实验环境&#xff1a; sqli-labs&#xff0c;小皮面板搭建&#xff0c;edge浏览器 apache&#xff1a;2.4.39&#xff0c;MySQL&#xff1a;5.7 PHP&#xff1a;5.39 Python&#xff08;pycharm2023&#xff09;:3 less-8 布尔盲注&#xff1a; 1.我这里是采用最简单的直接采…

基于SSM的农产品供销小程序+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、农户功能模块&#xff1a;用户管理、农户管理、产品分类管理、农产品管理、咨询管理、订单管理、收藏管理、购物车、充值、下单等技术选型&#xff1a;SSM&#xff0c;Vue&#xff08;后端管理web&#xff09;&#xff0c;uniapp等测试…

前端可以不用依赖后端实现导出大数据了

theme: channing-cyan hightlight: channing-cyan 前言 在我们公司表格数据导出都是前端去处理。一开始数据量不大&#xff0c;倒没什么问题。但随着数据量的加大&#xff0c;问题也逐渐暴露出来。 一天的数据量有一来万条&#xff0c;导出一定时间范围的数据&#xff0c;30…

游戏引擎学习第99天

仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板&#xff1a;制作一些光场(Light Field) 当前的目标是为游戏添加光照系统&#xff0c;并已完成了法线映射&#xff08;normal maps&#xff09;的管道&#xff0c;但还没有创建可以供这些正常映射采样的光场。为了继续推进&…

通过 Docker 安装和部署 KeyDB v6.3.4 的详细步骤

KeyDB 是一种高性能的开源内存数据库&#xff0c;最初是基于 Redis 项目开发的&#xff0c;但在性能、特性和功能上进行了许多增强和改进。它兼容 Redis 的大部分命令和数据结构&#xff0c;因此可以作为 Redis 的替代品使用&#xff0c;尤其是在需要更高性能和多线程支持的场景…

Android Studio 打包App问题

一、场景 windows 电脑C 盘空间越来越少&#xff0c;所有软件默认位置都往C盘用户目录写入数据&#xff0c;于是开始准备整理&#xff0c;Android Studio 相关的 .android 和 .gradle 目录成为了目标。 二、问题出现 1、将C盘的.gradle 目录拷贝到D盘&#xff0c;文件比较大&a…

鸿蒙HarmonyOS NEXT开发:横竖屏切换开发实践

文章目录 一、概述二、窗口旋转说明1、配置module.json5的orientation字段2、调用窗口的setPreferredOrientation方法 四、性能优化1、使用自定义组件冻结2、对图片使用autoResize3、排查一些耗时操作 四、常见场景示例1、视频类应用横竖屏开发2、游戏类应用横屏开发 五、其他常…

linux安装jdk 许可证确认 user did not accept the oracle-license-v1-1 license

一定要接受许可证&#xff0c;不然会出现 一、添加 ppa第三方软件源 sudo add-apt-repository ppa:ts.sch.gr/ppa二、更新系统软件包列表 sudo apt-get update三、接受许可证 echo debconf shared/accepted-oracle-license-v1-1 select true | sudo debconf-set-selection…

DeepSeek 助力 Vue 开发:打造丝滑的进度条

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

2025年SEO自动优化工具

随着2025年互联网的快速发展&#xff0c;越来越多的企业和个人意识到&#xff0c;拥有一个排名靠前的网站对于吸引客户、增加流量、提高转化率至关重要。而要想让自己的网站脱颖而出&#xff0c;获得更多曝光&#xff0c;最重要的一项工作就是进行SEO优化。传统的SEO优化方式通…

华硕笔记本怎么一键恢复出厂系统_华硕笔记本一键恢复出厂系统教程

华硕笔记本怎么一键恢复出厂系统&#xff1f; 华硕一键恢复出厂系统是一个安全、高效、方便的恢复方式&#xff0c;让您轻松还原出厂设置&#xff0c;以获得更好的系统性能。如果您的华硕电脑遇到问题&#xff0c;可以使用华硕一键恢复出厂系统功能。下面小编就教大家华硕笔记本…

Unity 编辑器热更C# FastScriptReload

工具源码&#xff1a;https://github.com/handzlikchris/FastScriptReload 介绍 用于运行时修改C#后能快速重新编译C#并生效&#xff0c;避免每次改C#&#xff0c;unity全部代码重新编译&#xff0c;耗时旧且需要重启游戏。 使用 需要手动调整AssetPipeline自动刷新模式&…

vue纯静态实现 视频转GIF 功能(附源码)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、实现后的效果二、使用步骤1.引入库2.下载or复制出来js3. 前端实现 总结 前言 一天一个小demo 今天来一个vue纯静态实现 视频转GIF 功能 上一篇我们讲到了…

因果机器学习(CausalML)前沿创新思路

结合了传统因果推断与机器学习的因果机器学习是目前AI领域的前沿研究方向&#xff0c;其核心优势在于将因果逻辑融入数据驱动模型&#xff0c;从根本上解决了传统方法的缺陷。因此&#xff0c;它也是突破传统机器学习瓶颈的关键方向&#xff0c;不仅当下热度高&#xff0c;在未…