鸿蒙开发中,高效复用组件提升UI性能及nodepool使用
一、概述
组件复用是指自定义组件从组件树上移除后被放入缓存池,后续在创建相同类型的组件节点时,直接复用缓存池中的组件对象。在应用开发时,组件复用是优化UI性能,确保应用流畅的重要手段。合理使用可复用组件,一方面,可以避免频繁创建和销毁对象的过程,减少内存回收的频率;另一方面,复用缓存中的组件可以直接绑定数据进行显示,与创建新视图相比,降低了计算开销,提升了显示效率。常见的组件复用开发场景是长列表滑动:在应用展示大量数据的列表界面中,当用户快速地进行滑动操作,列表项反复创建销毁可能导致卡顿等性能问题。这种情况下,使用组件复用机制可以重用已经创建过的列表项视图,提高滑动的流畅度。
二、使用场景
三、使用案例
场景:同一列表内的组件复用
1.列表项结构类型相同
@Component
export struct OneTypeItemPage {
// ...
build() {
NavDestination() {
Column() {
List() {
LazyForEach(this.dataSource, (item: ItemData) => {
// layout the component, and set reuse id (or no set with using name as default id)
ItemView({ title: item.title, from: item.from, tail: item.tail })
.reuseId('item_id')
}, (item: ItemData) => item.id.toString())
}
// ...
}
// ...
}
// ...
}
}
// add @Reusable to mark component
@Reusable
@Component
struct ItemView {
@State title: string | Resource = '';
@State from: string | Resource = '';
@State tail: string | Resource = '';
// update data in aboutToReuse method
aboutToReuse(params: Record<string, Object>): void {
this.title = params.title as string;
this.from = params.from as string;
this.tail = params.tail as string;
}
build() {
// ...
}
}
2.列表项结构类型不同
@Component
export struct MultiTypeItemPage {
// ...
build() {
NavDestination() {
Column() {
List() {
LazyForEach(this.dataSource, (item: ItemData) => {
if (item.type === 0) {
TextTypeItemView({ item: item })
.reuseId('text_item_id')
} else if (item.type === 1) {
ImageTypeItemView({ item: item })
.reuseId('image_item_id')
} else if (item.type === 2) {
ThreeImageTypeItemView({ item: item })
.reuseId('three_image_item_id')
}
}, (item: ItemData) => item.id.toString())
}
// ...
}
// ...
}
// ...
}
}
@Reusable
@Component
struct TextTypeItemView {
// ...
}
@Reusable
@Component
struct ImageTypeItemView {
// ...
}
@Reusable
@Component
struct ThreeImageTypeItemView {
// ...
}
3.列表项内子组件可拆分组合
@Component
export struct ComposableItemPage {
// ...
@Builder
itemBuilderSingleImage(item: ItemData) {
TopView({ item: item }).reuseId('top_id')
MiddleSingleImageView({ item: item }).reuseId('middle_image_id')
BottomView({ item: item }).reuseId('bottom_id')
}
@Builder
itemBuilderThreeImage(item: ItemData) {
TopView({ item: item }).reuseId('top_id')
MiddleThreeImageView({ item: item }).reuseId('middle_three_image_id')
BottomView({ item: item }).reuseId('bottom_id')
}
@Builder
itemBuilderVideoImage(item: ItemData) {
TopView({ item: item }).reuseId('top_id')
MiddleVideoView({ item: item }).reuseId('middle_video_id')
BottomView({ item: item }).reuseId('bottom_id')
}
build() {
NavDestination() {
Column() {
List() {
LazyForEach(this.dataSource, (item: ItemData) => {
ListItem() {
Column() {
if (item.type === 0) {
this.itemBuilderSingleImage(item)
} else if (item.type === 1) {
this.itemBuilderThreeImage(item)
} else if (item.type === 2) {
this.itemBuilderVideoImage(item)
}
}
// ...
}
}, (item: ItemData) => item.id.toString())
}
// ...
}
// ...
}
// ...
}
}
@Reusable
@Component
struct TopView {
// ...
}
@Reusable
@Component
struct BottomView {
// ...
}
@Reusable
@Component
struct MiddleSingleImageView {
// ...
}
@Reusable
@Component
struct MiddleThreeImageView {
// ...
}
@Reusable
@Component
struct MiddleVideoView {
// ...
}
场景:多个列表间的组件复用
1.概述
在ArkUI中,可以采用Swiper+List实现这种功能场景,其中Swiper中的每个页面都使用一个List列表呈现内容。从@Reusable的复用机制可知,复用缓存池需要在同一父组件中,而列表项Item的父组件是当前页面的列表List,当Swiper内的页面切换时,无法直接复用上一个页面的列表项。
2.说明
为什么不使用Tabs+List,而是用Swiper+List组件实现?
当前Tabs内容页不支持使用LazyForEach,只能使用ForEach+TabContent。如果使用ForEach,Tabs页面显示时会一次性将所有的TabContent创建,TabContent子页面切换时也不会执行aboutToDisappear(),无法回收组件,进而不存在复用优化的可能 。
3.推荐使用
实现的全局组件复用池三方库:nodepool。
4.使用案例
1.创建可复用的自定义组件
import { NodeItem, CustomNodePoolFactory } from '@hadss/nodepool';
class Params {
text: string = "this is a text";
constructor(text: string) {
this.text = text;
}
}
@Builder
function ButtonBuilder(params: Params) {
Column() {
Button(`button ` + params.text)
.borderWidth(2)
.backgroundColor(Color.Orange)
.width("100%")
.height("100%")
.gesture(
TapGesture()
.onAction((event: GestureEvent) => {
console.log("TapGesture");
})
)
}
.width('100%')
.height(300)
.backgroundColor(Color.Gray)
}
2.创建Builder及复用池类型
let btnBuilder: WrappedBuilder<ESObject> = wrapBuilder<ESObject>(ButtonBuilder);
const REUSE_VIEW_TYPE_SWIPER: string = 'reuse_type_swiper_';
3.创建一个CustomNodePoolFactory类型的对象,通过多例模式预创建NodePool复用池,同时通过单例模式获取创建NodePool组件复用池,根据传入的type类型查找复用池中是否存在可复用的组件,如果有则直接使用,如果没有则重新创建。使用NodeContainerProxy组件占位,从复用池NodePool中获取组件加载到页面中
@Entry
@Component
struct Index {
private nodePoolFactory: CustomNodePoolFactory = new CustomNodePoolFactory();
private controller: NodeItem | undefined;
private typeCfg: TypeReuseConfig = {
type: REUSE_VIEW_TYPE_SWIPER,
expirationTime: 30 * 60 * 1000, // 老化时间
reuseCallback: this.reuseCallback,
recycleCallback: this.recycleCallback
}
// 组件复用生命周期回调
private reuseCallback(item: NodeItem): void {
console.log('tag1', `reuseCallback, id:${item.id}`);
}
// 组件回收生命周期回调
private recycleCallback(item: NodeItem): void {
console.log('tag1', `recycleCallback, id:${item.id}`);
}
aboutToAppear(): void {
this.nodePoolFactory.getCommonNodePool().setTypeReuseConfig(this.typeCfg);
// 组件预创建
let res = this.nodePoolFactory.getNodePool().preCreateNode(REUSE_VIEW_TYPE_SWIPER, {
text: 'hello'
}, btnBuilder, this.getUIContext());
// 组件复用
this.controller = this.nodePoolFactory.getCommonNodePool().getNode(REUSE_VIEW_TYPE_SWIPER, {
text: 'hello'
}, btnBuilder);
}
build() {
Column() {
NodeContainerProxy({ nodeItem: this.controller })
Text("点我进行参数传递和触摸事件传递")
.width('100%')
.height(300)
.backgroundColor(Color.Pink)
.onTouch((event) => {
if (event != undefined) {
this.controller?.postTouchEvent(event); // 触摸事件传递
this.controller?.node?.update(new Params("on update data")); // 参数传递
}
})
}
}
}
更多推荐


所有评论(0)