概述
在滑动场景下,常常会对同一类自定义组件的实例进行频繁的创建与销毁。此时可以考虑通过组件复用减少频繁创建与销毁的能耗。组件复用时,可能存在许多影响组件复用效率的操作,本篇文章将重点介绍如何通过组件复用四板斧提升复用性能。
组件复用四板斧:
- 减少组件复用的嵌套层级,如果在复用的自定义组件中再嵌套自定义组件,会存在节点构造的开销,且需要在每个嵌套的子组件中的aboutToReuse方法中实现数据的刷新,造成耗时。
- 优化状态管理 “精准控制组件刷新规范”,在复用的场景下,需要控制状态变量的刷新范围,避免扩大刷新范围,降低组件复用的效率。
- 复用组件嵌套结构会变更的场景,使用reuseId标记不同结构的组件构成,如:使用if else结构来控制组件的创建,会造成组件树结构的大幅变动,降低组件复用的效率。需使用reuseId标记不同的组件结构,提升复用性能。
- 第四板斧,不要使用函数/方法作为复用组件的入参,复用时会触发组件的构造,如果函数入参中存在耗时操作,会影响复用性能。
组件复用原理机制
-
如上图①中,ListItem N-1滑出可视区域即将销毁时,如果标记了@Reusable,就会进入这个自定义组件所在父组件的复用缓存区。需注意在自定义组件首次显示时,不会触发组件复用。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。尤其是该复用组件具有相同的布局结构,仅有某些数据差异时,通过组件复用可以提高列表页面的加载速度和响应速度。
-
如上图②中,复用缓存池是一个Map套Array的数据结构,以reuseId为key,具有相同reuseId的组件在同一个Array中。如未设置reuseId,则reuseId默认是自定义组件的名字。
-
如上图③中,发生复用行为时,会自动递归调用复用池中取出的自定义组件的aboutToReuse回调,应用可以在这个时候刷新数据。
复用组件嵌套结构会变更的场景,使用reuseId标记不同结构的组件构成
在自定义组件复用的场景中,如果使用if/else条件语句来控制布局的结构,会导致在不同逻辑创建不同布局结构嵌套的组件,从而造成组件树结构的不同。此时我们应该使用reuseId来区分不同结构的组件,确保系统能够根据reuseId缓存各种结构的组件,提升复用性能。正反例如下:
反例:
@Entry
@Component
struct withoutReuseId {
aboutToAppear(): void {
getFriendMomentFromRawfile();
}
build() {
Column() {
TopBar()
List({ space: ListConstants.LIST_SPACE }) {
LazyForEach(momentData, (moment: FriendMoment) => {
ListItem() {
OneMoment({
moment: moment,
fontSize: moment.size
})
}
}, (moment: FriendMoment) => moment.id)
}
.cachedCount(Constants.CACHED_COUNT)
}
}
}
@Reusable
@Component
export struct OneMoment {
@Prop moment: FriendMoment;
build() {
Column() {
...
Text(this.moment.text)
if (this.moment.image !== '') {
Flex({ wrap: FlexWrap.Wrap }) {
Image($r(this.moment.image))
Image($r(this.moment.image))
Image($r(this.moment.image))
Image($r(this.moment.image))
}
}
...
}
}
}
上述反例的操作中,通过if来控制组件树走不同的分支,分别选择是否创建Flex组件。导致更新if分支时仍然可能走删除重创的逻辑。考虑采用根据不同的分支设置不同的reuseId来提高复用的性能。
优化前,2号列表项复用时长为3ms。
正例:
@Entry
@Component
struct withoutReuseId {
aboutToAppear(): void {
getFriendMomentFromRawfile();
}
build() {
Column() {
TopBar()
List({ space: ListConstants.LIST_SPACE }) {
LazyForEach(momentData, (moment: FriendMoment) => {
ListItem() {
OneMoment({moment: moment})
// 使用reuseId进行组件复用的控制
.reuseId((moment.image !== '') ? 'withImage' : 'noImage')
}
}, (moment: FriendMoment) => moment.id)
}
.cachedCount(Constants.CACHED_COUNT)
}
}
}
@Reusable
@Component
export struct OneMoment {
@Prop moment: FriendMoment;
build() {
Column() {
...
Text(this.moment.text)
if (this.moment.image !== '') {
Flex({ wrap: FlexWrap.Wrap }) {
Image($r(this.moment.image))
Image($r(this.moment.image))
Image($r(this.moment.image))
Image($r(this.moment.image))
}
}
...
}
}
}
上述正例的操作中,通过reuseId来标识需要复用的组件,省去走if删除重创的逻辑,提高组件复用的效率和性能。
优化效果
针对列表滑动场景中单个列表项中的一个包含4个Image组件的Flex容器组件,通过if进行条件渲染,存在不同逻辑创建不同布局结构嵌套的组件的情况,反例中没有使用复用标识reuseId,正例中采用复用标识reuseId区分不同结构的组件。
优化后,2号列表项复用时长缩短为2ms。
所以,Trace数据证明,针对不同逻辑创建不同布局结构嵌套的组件的情况,通过使用reuseId来区分不同结构的组件,能减少删除重创的逻辑,提高组件复用的效率和性能。
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
点击领取→【纯血版鸿蒙全套最新学习资料】(安全链接,放心点击)希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
鸿蒙(HarmonyOS NEXT)最新学习路线
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料