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 = trueindex: number = 0controllerVideo: VideoController = new VideoController();aboutToReuse(params){this.webimg = params.webimgthis.img = params.imgthis.hei = params.heithis.introduce = params.introducethis.price = params.pricethis.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.ppmy.cn/server/7176.html

相关文章

深入了解Web3:区块链技术如何改变我们的数字世界

引言 在过去的几年中&#xff0c;Web3和区块链技术逐渐成为了技术界和社会大众关注的焦点。从初始的加密货币到现在的去中心化应用&#xff08;DApps&#xff09;和智能合约&#xff0c;区块链技术已经开始改变我们的数字世界的面貌。在本文中&#xff0c;我们将深入探讨Web3和…

【网络】Burpsuite学习笔记

文章目录 1.介绍1.1 正常客户端与服务端通信&BurpSuite代理后1.2 下载激活参考地址1.3 代理设置1.4 Proxy SwitchyOmega 使用1.4.1 新建情景模式1.4.2 设置代理1.4.2 应用选项 1.5 FoxyProxy 使用1.6 安装证书1.6.1 方式一1.6.2 方式二1.6.3 浏览器安装证书1.6.4 或者直接双…

Windows10系统中忘记MySQL数据库root权限登录密码

本文档所使用的MySQL版本为MySQL5.7>> mysqld_safe --skip-grant-tables&mysql -u root mysql在命令行中使用上面的命令登录MySQL&#xff0c;其中--skip-grant-tables允许用户跳过权限表进行无密码登录 >> UPDATE user SET authentication_stringPASSWORD(&q…

Dockerfile与docker-compose容器编排(Docker系列第3章,共3章)

温馨提示 不同的组件需要不同的docker配置&#xff08;例如容器卷&#xff0c;端口&#xff0c;环境变量等&#xff09;&#xff0c;使用你懂得工具看hub.docker.com官方手册。不要死记硬背&#xff0c;因为这种东西不是天天要写&#xff0c;过段时间就忘了&#xff0c;很容易…

python-自动化篇-终极工具-用GUI自动控制键盘和鼠标-pyautogui-键盘

文章目录 键盘键盘——记忆宫殿入门——通过键盘发送一个字符串——typewrite()常规——键名——typewrite()常规——按下键盘——keyDown()常规——释放键盘——keyUp()升级——热键组合——hotkey() 键盘 pyautogui也有一些函数向计算机发送虚拟按键&#xff0c;让你能够填充…

【创建git仓库并关联github账户】

创建git仓库并关联github账户 介绍 介绍 将文件夹创建为git仓库并与GitHub上的对应git地址关联&#xff1a; 打开终端或命令提示符&#xff0c;并导航到你想要创建git仓库的文件夹。 使用以下命令初始化git仓库&#xff1a; git init添加你的文件到暂存区&#xff1a; git…

在Python中使用gmssl包实现SM2加密和解密

1.安装gmssl包 pip install gmssl安装完成后&#xff0c;您可以使用 gmssl 提供的函数来修改 User 类中的 set_password 和 verify_password 方法&#xff0c;以便使用 SM2 加密和解密密码。以下是使用 gmssl 的 User 类示例&#xff1a; import datetime from tortoise.model…

vue-textarea光标位置插入指定元素

vue-textarea光标位置插入指定元素 需求 点击插入关键字的时候把内容插入到光标所在的位置 效果图 实现 html <div class"temlate-container"><div class"template-content"><el-inputref"modelContent"v-model"mould.m…

【软考】敏捷方法

目录 一、概念二、敏捷方法2.1 极限编程(XP)2.2 水晶法(Crystal)2.2.1 说明2.2.1 特征 2.3 并列争球法(Scrum)2.4 自适应软件开发(ASD)2.5 敏捷统一过程(AUP)2.5.1 说明2.5.2 执行的活动 一、概念 1.Agile Development。 2.敏捷开发的总体目标是通过“尽可能早地、持续地对有价…

05 MySQL--字段约束、事务、视图

1. CONSTRAINT 约束 创建表时&#xff0c;可以给表的字段添加约束&#xff0c;可以保证数据的完整性、有效性。比如大家上网注册用户时常见的&#xff1a;用户名不能为空。对不起&#xff0c;用户名已存在。等提示信息。 约束包括&#xff1a; 非空约束&#xff1a;not null检…

Verilog仿真跨模块调用内部信号的方法

在Verilog仿真时如果需要调用某子模块中的信号在本模块中使用可以使用层次化引用的方法&#xff0c;而不需要在rtl部分用端口引出来。 引用方式&#xff1a;当前例化模块名.子例化模块名.子子例化模块名.参数 将需要的信号引出。 注意是用例化模块名而不是用子模块名&#xff…

const成员函数 以及 取地址及const取地址操作符重载

目录 const成员函数 结论&#xff1a; 取地址及const取地址操作符重载 const成员函数 将const 修饰的 “ 成员函数 ” 称之为 const成员函数 &#xff0c; const 修饰类成员函数&#xff0c;实际修饰该成员函数的&#xff08;*this&#xff09; &#xff0c;表明在该成员函数…

代码随想录算法训练营第四十六天| 139.单词拆分,关于多重背包,你该了解这些!, 背包问题总结篇!

题目与题解 参考资料&#xff1a;背包问题总结 139.单词拆分 题目链接&#xff1a;139.单词拆分 代码随想录题解&#xff1a;139.单词拆分 视频讲解&#xff1a;动态规划之完全背包&#xff0c;你的背包如何装满&#xff1f;| LeetCode&#xff1a;139.单词拆分_哔哩哔哩_bilib…

1.MMD模型动作场景镜头的导入及视频导出

界面介绍 MIKUMIKUDANCE926版本 MMD的工具栏模型骨骼帧的窗口&#xff0c;在不同时间做不同动作&#xff0c;可以在这里打帧操作时间曲线操作窗口&#xff0c;控制模型两个动作之间的过渡模型操作窗口&#xff0c;导入模型选择模型相机操作&#xff0c;控制相机远近&#xf…

OracleDay01

ORACLE 简介 什么是 ORACLE ORACLE 数据库系统是美国 ORACLE 公司&#xff08;甲骨文&#xff09;提供的以分布式数据库为核心的一组软件产品&#xff0c;是目前最流行的客户/服务器(CLIENT/SERVER)或 B/S 体系结构的数据库之一。 ORACLE 通常应用于大型系统的数据库产品。 ORA…

C#:闰年判断

任务描述 编写程序&#xff0c;完成如下功能&#xff1a;从键盘输入任意年份year&#xff0c;判别该年份是否闰年。 为避免出现格式错误&#xff0c;请直接拷贝粘贴题目中给的格式字符串和提示信息到你的程序中。 ####输入 从键盘任意输入一个年份 输出 根据是否闰年进行输出…

数据结构(七)——B树和B+树

7.4.1_1 B树 5叉查找树 //5叉排序树的结点定义 struct Node {ElemType keys[4]; //最多4个关键字struct Node &child[5]; //最多5个孩子int num; //结点中有几个关键字 }; 如何保证查找效率&#xff1f; eg:对于5叉排序树&#xff0c;规定…

03 JavaScript学习:浏览器中执行 JavaScript

我比较习惯Chrome浏览器。 Chrome 是由 Google 开发的免费网页浏览器&#xff0c;调试代码非常方便。 Chrome 官网地址&#xff1a;https://www.google.com/intl/zh-CN/chrome/。 控制台输出 浏览器的控制台是开发者工具中的一个重要组成部分&#xff0c;它可以用来查看网页…

uniapp 树状数据无限极 进行展示并选择

两个文件 demo.vue – olylis-cascader.vue 提示&#xff1a;可以对照接口返回的参数名来修改&#xff0c;把valueid&#xff0c;labelname demo.vue <template><olylis-cascader :options"treeData" :value"selectedValue" change"handle…

2024-04-15 问AI: 在深度学习中,什么是过拟合?

文心一言 在深度学习中&#xff0c;过拟合&#xff08;Overfitting&#xff09;是指模型在训练数据上表现很好&#xff0c;但在测试数据或新数据上表现较差的现象。换句话说&#xff0c;过拟合的模型对训练数据中的噪声或随机波动也进行了学习&#xff0c;导致其在面对新数据时…