@Reusable 不会帮你重置状态

长列表用了 LazyForEach,又给卡片加了 @Reusable,第一屏正常。往下滚再滚回来,部分卡片的文字、图片、点击态变成了之前显示过的内容。去掉 @Reusable 就恢复正常——问题不在数据源,在复用路径。

@Reusable 的机制是:组件离屏后进入复用池,新数据上屏时框架直接取旧实例挂回组件树,不会帮你重置状态。文字和图片你在 aboutToReuse 里更新了,但 @State isSelected、展开折叠、点赞态这些交互状态如果漏了,滚回来就还是上一个 item 的值。

aboutToReuse 必须覆盖所有驱动 UI 的状态

typescript

@Reusable
@Component
struct CardView {
  @State item: CardItem = new CardItem('', '', '', false);
  @State isLiked: boolean = false;

  aboutToReuse(params: Record<string, Object>): void {
    const next = params.item as CardItem;
    this.item = next;
    this.isLiked = next.isLiked;   // 交互状态也要按新数据重置
  }

  build() {
    Column() {
      Image(this.item.imageUrl)
      Text(this.item.title)
      Button(this.isLiked ? '已赞' : '点赞')
        .onClick(() => { this.isLiked = !this.isLiked; })
    }
  }
}

aboutToReuse 是复用的唯一入口。所有会被 UI 读取的 @State 变量,都必须在这里按新数据重新赋值。普通成员变量(没加 @State)在复用时不会走状态更新,也不受 aboutToReuse 参数覆盖,只能手动赋值。

常见错误有三个。第一,aboutToReuse 只更新了数据字段,漏掉 isSelected、isExpanded 这类交互态。第二,卡片内部用 if/else 控制有图/无图两种布局,复用时组件树结构和缓存的不一致,框架会创建或销毁子组件。这种要用 .reuseId() 把不同布局形态分到不同的复用组。第三,给 ForEach 里的组件加 @Reusable 但外层没有换成 LazyForEach,根本没有进入懒加载路径,复用收益为零。

LazyForEach 负责“按需创建”,@Reusable 负责“离屏回收再复用”,两者是一对。少了任何一个,长列表的滚动成本都会以另一种方式回来。先把 aboutToReuse 写完整,再谈帧率优化。

Logo

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

更多推荐