上节课我们搞定了 V2 的状态驱动。有了好的数据模型,如果一拉列表就卡成 PPT,用户体验依然是灾难。几乎所有的大型应用都绕不开长列表,很多开发者第一反应就是“我用 LazyForEach 了呀,为什么还是卡?”。

其实,LazyForEach 只解决了一个问题:不要一次性创建一万个组件节点。但它不负责“组件如何被复用”,也不负责“单个 Item 本身有多重”。如果你在 LazyForEach 里用的 key 是 index,或者没有配置组件复用池,滑动起来照样白屏闪烁,内存照样飙升。今天我们把长列表优化的底裤扒干净。

稳定的Key是正确渲染的基石

在 LazyForEach 的数据源接口中,必须实现 getData(index) 和 keyGenerator。很多新手在这里为了省事,直接返回 index.toString()。这是万恶之源。

当你在列表顶部插入一条新数据时,原本位置为 0 的数据被挤到了 1。如果 key 还是按 index 生成,框架会认为位置 0 还是那个老组件,只是数据变了。但实际上,用户眼里的数据和状态全错位了。图片加载错乱、输入框里的字串行,全是这个原因。

typescript

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private dataArray: any[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): any {
    return this.dataArray[index];
  }

  // 核心:必须使用唯一业务标识,绝对不能用 index
  public getKey(index: number): string {
    return this.dataArray[index].id; 
  }

  // 注册/注销监听器逻辑略
}

使用唯一的业务 id 作为 key。执行结果就是,当你插入或删除数据时,框架能精准识别出哪些组件实例可以留用,哪些需要销毁,组件状态就不会错乱。常见错误是在数据不够唯一时,拿数据内容的哈希当 key,内容一改组件就重建,反而拖垮性能。

开启组件复用@Reusable

仅仅数据源对了还不够。如果你的列表项里包含复杂的图片、文本甚至视频,每次划出屏幕就被销毁,滑回来又要重新构建,开销依然很大。V2 架构下,我们要配合 @Reusable 把组件变成可复用的“空壳”。

只要给自定义组件加上 @Reusable,当它被 LazyForEach 移出可视区时,它不会被彻底回收,而是放入缓存池。下次新数据滑入时,直接拿出这个旧实例,并触发 aboutToReuse 回调,把你需要的新数据塞进去。

typescript

@Reusable
@ComponentV2
struct ListItemCard {
  @Local title: string = '';
  @Local isSelected: boolean = false;

  // 复用发生时,一定要在这里重置所有可变状态
  aboutToReuse(params: Record<string, Object>): void {
    this.title = params.title as string;
    // 重点:上个组件的选中状态千万不要带过来
    this.isSelected = false; 
  }

  build() {
    Row() {
      Text(this.title)
      if (this.isSelected) {
        Text('已选中').fontColor(Color.Red)
      }
    }
  }
}

看懂了吗?aboutToReuse 就是在这个组件重新登场前,给它重新洗脑,把旧数据洗干净。执行结果就是列表极其丝滑。常见错误就是漏写了 aboutToReuse,或者在里面忘了重置某个内部的 UI 状态(比如弹窗开关、选中态),导致滑动几页后,UI 像幽灵一样串号。

控制缓存数量与布局扁平化

LazyForEach 有一个 cachedCount 属性。设得太小,滑动稍微快一点就露白底;设得太大,内存直接爆炸。通常建议设在 3 到 5 之间,具体看你 item 里有没有大图。

另外,很多开发者为了设计稿还原,在 ListItem 里套了三层 Column 再套两层 Row。长列表滑动时,每一帧都在进行极其复杂的布局测量。能用 RelativeContainer 的地方别用多层嵌套,能用基础组件的,别包一层无意义的容器。把 Item 的布局层级降到最低,才是流畅的根本。

长列表优化不是玄学,核心就三步:给一个绝不重复的业务 ID,开 @Reusable 并在回调里洗脑重置状态,管好缓存数量并压平布局。这三板斧下去,就算几万条数据,也不会再出现卡顿掉帧。下节课,我们把视野拉宽,看看怎么用 GridRow 搞定多端设备的断点适配。

Logo

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

更多推荐