一、概述

组件复用是指自定义组件从组件树上移除后被放入缓存池,后续在创建相同类型的组件节点时,直接复用缓存池中的组件对象。在应用开发时,组件复用是优化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")); // 参数传递
          }
        })
    }
  }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐