HarmonyOS鸿蒙Next中使用@Reusable优化组件性能

HarmonyOS鸿蒙Next中使用@Reusable优化组件性能

如何使用@Reusable优化组件性能,告别列表卡顿?

3 回复

实现思路

1、首先,确定应用中是否存在包含复杂UI的长列表场景。如果列表项的UI结构简单,@Reusable 带来的提升可能不明显。但如果列表项是复杂的卡片,包含图片、按钮、多个布局嵌套,那么它就是优化的绝佳对象。

2、在自定义组件的定义前,添加 @Reusable 装饰器。这相当于告诉ArkUI编译器:“这个组件的实例可以被缓存和复用”。

3、实现 aboutToReuse 生命周期,与组件首次创建时调用的 aboutToAppear 不同,aboutToReuse 会在组件从缓存池中被取出、准备复用时调用。

4、在父组件中,像往常一样使用 LazyForEach 来渲染数据。ArkUI框架会自动识别被 @Reusable 标记的组件,并在后台管理其复用逻辑,开发者无需在父组件中编写额外的复用代码。

应用场景

社交应用的信息流:每一条动态都是一个复杂的卡片,包含头像、昵称、正文、图片、点赞/评论按钮等。快速滑动时,@Reusable 可以让滑动如丝般顺滑。

电商应用的商品列表:商品卡片通常包含主图、标题、价格、标签等多个元素,结构复杂。@Reusable 能有效提升用户浏览商品列表的体验。

实现效果

cke_6014.png

完整代码

注意案例中

// ItemData.ets
export class ItemData {
  id: string;
  title: string;
  content: string;
  imageUrl: string; // 使用资源管理器中的像素图

  constructor(id: string, title: string, content: string, imageUrl: string) {
    this.id = id;
    this.title = title;
    this.content = content;
    this.imageUrl = imageUrl;
  }
}
// ReusableListItem.ets
import { ItemData } from './ItemData';

/**
 * 使用 [@Reusable](/user/Reusable) 装饰器标记此组件为可复用组件
 */
[@Reusable](/user/Reusable)
@Component
export struct ReusableListItem {
  // 使用 @Prop 接收从父组件传递的数据
  @Prop itemData: ItemData;

  // 内部状态,例如用于模拟一个“喜欢”的状态
  @State isLiked: boolean = false;

  aboutToAppear() {
    console.info(`[Lifecycle] Component ${this.itemData.id} aboutToAppear. First time created.`);
  }

  /**
   * 组件复用时触发的生命周期函数
   * @param params 从父组件传入的新参数,与 @Prop 对应
   */
  aboutToReuse(params: Record<string, Object>) {
    // 必须在这里更新 @Prop 变量,否则UI不会刷新
    this.itemData = params.itemData as ItemData;
    // 如果有其他需要根据数据重置的状态,也可以在这里处理
    this.isLiked = false;
    console.info(`[Lifecycle] Component ${this.itemData.id} aboutToReuse. Reusing existing instance.`);
  }

  build() {
    Column() {
      Row() {
        Image($r('app.media.background')) // 假设有一个本地图标资源
          .width(48)
          .height(48)
          .borderRadius(24)
          .margin({ right: 12 })

        Column() {
          Text(this.itemData.title)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')
          Text(this.itemData.content)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 4 })
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)
      }
      .width('100%')
      .alignItems(VerticalAlign.Top)
      .padding(16)

      Divider().color('#F1F1F1')

      Row() {
        Button(this.isLiked ? '已喜欢' : '喜欢')
          .fontSize(14)
          .backgroundColor(this.isLiked ? '#FF6B6B' : '#E1E1E1')
          .fontColor(this.isLiked ? Color.White : '#333333')
          .onClick(() => {
            this.isLiked = !this.isLiked;
          })

        Blank()

        Button('分享')
          .fontSize(14)
          .backgroundColor('#007DFF')
          .fontColor(Color.White)
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 16 })
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .backgroundColor(Color.White)
    .borderRadius(12)
    .margin({ left: 12, right: 12, top: 8, bottom: 8 })
    .shadow({
      radius: 4,
      color: '#1F000000',
      offsetX: 0,
      offsetY: 2
    })
  }
}
// Index.ets
import { ItemData } from './ItemData';
import { ReusableListItem } from './ReusableListItem';

// 模拟大量数据
const generateData = (): ItemData[] => {
  let data: ItemData[] = [];
  for (let i = 0; i < 100; i++) {
    data.push(new ItemData(
      `id_${i}`,
      `这是第 ${i + 1} 个标题`,
      `这是第 ${i + 1} 个卡片的内容描述,它可能很长,用来测试文本溢出和多行显示的效果。组件复用技术能够显著提升此类复杂列表的滚动性能。`,
      '' // 图片URL留空,使用本地资源
    ));
  }
  return data;
}

@Entry
@Component
struct Index {
  // 使用 @State 管理数据源
  @State private dataSource: ItemData[] = generateData();

  build() {
    Column() {
      Text('[@Reusable](/user/Reusable)性能优化示例')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 10 })

      List({ space: 0 }) {
        // 注意注意注意:这里一定要使用 LazyForEach 进行数据懒加载 ,我这里为了方便只写了ForEach
        ForEach(this.dataSource, (item: ItemData) => {
          // 直接使用被 [@Reusable](/user/Reusable) 标记的组件
          // ArkUI 框架会自动处理其复用逻辑
          ReusableListItem({ itemData: item })
        }, (item: ItemData) => item.id) // keyGenerator 必须提供,用于标识唯一项
      }
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.Spring)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

更多关于HarmonyOS鸿蒙Next中使用@Reusable优化组件性能的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


@Reusable

@Reusable是ArkUI组件复用装饰器,用于标记可复用的自定义组件。当组件从组件树上移除后,框架会将其加入复用缓存。后续创建同类型组件时,若缓存中存在,则会优先复用缓存的组件节点,从而省去初始创建过程,提升性能。

主要适用于组件结构较复杂、创建成本较高且会被频繁创建/销毁的场景。使用时需注意组件复用的前提条件,例如其状态管理需能适应复用场景。

在HarmonyOS Next中,@Reusable装饰器是优化组件性能、特别是解决长列表滚动卡顿问题的关键工具。其核心原理是组件复用,而非传统的销毁后重建。

当列表滚动、组件滑出视窗时,被@Reusable标记的组件不会被销毁,而是被框架回收至一个复用池中。当需要渲染新的同类组件时,框架会优先从池中取出实例,直接应用新的数据(通过aboutToReuse生命周期)进行更新,跳过了完整的初始创建过程。

如何使用:

  1. 装饰组件:在自定义组件类前添加@Reusable装饰器。
    @Reusable
    @Component
    struct ReusableListItem {
      // 组件内容
    }
    
  2. 实现关键生命周期:在组件内实现aboutToReuse方法。当组件被复用时,框架会调用此方法并传入新的参数。在此方法中,你必须更新组件的所有状态(@State, @Link等)和通过参数传入的数据,以正确显示新内容。
    @Reusable
    @Component
    struct ReusableListItem {
      @State itemData: string = '';
    
      aboutToReuse(params: { data: string }) {
        // 必须在此处更新状态,以响应复用后的新数据
        this.itemData = params.data;
      }
    
      build() {
        // 使用 this.itemData 构建UI
      }
    }
    

性能收益与注意事项:

  • 显著减少开销:避免了组件初始化、视图树创建等大量计算,使列表滚动极其流畅。
  • 状态管理是关键aboutToReuse是复用的核心,必须在此方法中完成所有状态同步。如果状态更新不全,会导致UI显示错误数据。
  • 适用场景:最适合结构相似、仅内容数据变化的列表项组件。对于结构差异巨大的组件,复用意义不大。
  • @Component共存@Reusable@Component的增强,两者需同时使用。

通过正确应用@Reusable,可以极大提升列表等高频创建/销毁场景的渲染性能,实现丝滑滚动体验。

回到顶部