鸿蒙开发笔记-11-LazyForEach 数据懒加载

前面文章我们介绍过@Reusable装饰器实现组件实例的复用机制以提升性能, 其中示例用到了LazyForEach,也就是本章的主角。LazyForEach是鸿蒙ArkUI框架中实现高性能列表渲染的核心组件,其核心设计思想基于动态加载和资源回收机制。

  • LazyForEach基于动态加载+视图复用的双重优化机制
  • LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载
  • LazyForEach必须和@Reusable装饰器一起使用才能触发节点复用
  • LazyForEach必须使用DataChangeListener对象来进行更新
  • 可视区域优先:仅渲染当前屏幕可视区域内的列表项(如List的可见部分)
  • 缓冲区策略:通过cachedCount配置载前后缓冲区(默认0),预加载不可见区域的部分元素,降低滑动白屏概率,推荐值为可视区域能显示项数的1-2倍
  • 节点回收:使用@Reusable装饰器标记可复用组件,滑出可视区的组件存入复用池优先复用
  • 组件结构:每个迭代必须返回单一根组件,支持嵌套条件渲染
  • 唯一性原则:同一容器内只能存在一个LazyForEach,且不支持嵌套其他迭代组件

与ForEach的关键差异

特性 LazyForEach ForEach
渲染策略 按需加载+节点回收 全量渲染
内存占用 动态控制(更低) 固定(更高)
适用场景 长列表/复杂项 短列表/简单项
性能优化 自动回收+复用 无特殊优化
数据更新 需DataChangeListener 直接响应式更新

基础使用示例

scss 复制代码
@Entry
@Component
struct DemoList {
    private data: MyDataSource = new MyDataSource();

    build() {
        List({ space: 10 }) {
            LazyForEach(this.data, (item) => {
                @Reusable
                @Component
                struct ListItem {
                    @Prop item: string
                    build() {
                        Row() {
                            Image(item.avatar).width(50).height(50)
                            Column() {
                                Text(item.name).fontSize(16)
                                Text(item.description).opacity(0.6)
                            }
                        }
                    }
                }
                ListItem({ item })
            }, (item) => item.id.toString())
            .cachedCount(3) // 缓冲区配置
        }
    }
}

数据源实现

  • 必须实现IDataSource接口:
kotlin 复制代码
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = [];

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

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

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }
}

数据更新

graph TD A[数据变更] --> B{操作类型} B -->|添加| C[notifyDataAdd] B -->|删除| D[notifyDataDelete] B -->|修改| E[notifyDataChange] B -->|移动| F[notifyDataMove]

严禁直接修改数据源数组,必须通过监听器通知变更

@Reusable装饰器深度集成:复用机制实现

typescript 复制代码
@Reusable
@Component
struct NewsListItem {
  @Prop article: Article;
  
  aboutToReuse(params: { newArticle: Article }) {
    this.article = params.newArticle; // 复用时的状态更新
  }

  build() {
    Column() {
      NetworkImage(this.article.cover)
      Text(this.article.title)
    }
  }
}

注意事项:

  1. 复用组件必须使用@Prop而非@Link接收数据
  2. 避免在复用组件中使用@StorageLink等持久化状态装饰器

复用性能优化策略

优化方向 实施方法 效果
内存优化 设置.maxReuseCount(10)限制复用池大小 减少30%内存占用
渲染优化 aboutToReuse中重置动画状态 避免复用导致的动画残留
网络优化 使用Image.syncLoad(true)预加载图片 降低图片闪烁概率

常见问题

  • 问题1:列表项错乱

    • 根因:键值生成规则不唯一
    • 解决方案:避免使用index作为唯一键,采用复合键${id}_${timestamp}
  • 问题2:图片闪烁

    • 根因:复用导致Image重新加载
    • 优化方案:
    typescript 复制代码
    @Reusable
    @Component
    struct StableImage {
      @Prop url: string;
      private cachedImage = new LRUCache(20);
      
      build() {
        Image(this.cachedImage.get(this.url) || fetchImage(this.url))
      }
    }
  • 索引错位问题

    • 场景:删除中间项后点击事件错位
    • 解决方案:
    kotlin 复制代码
    typescript
    // 使用唯一标识代替index
    onClick(() => {
        const targetId = this.data.getId(index);
        this.data.deleteById(targetId);
    })
  • 异构列表实现

typescript 复制代码
LazyForEach(dataSource, (item) => {
  if (item.type === 'BANNER') {
    BannerItem(item)
  } else if (item.type === 'VIDEO') {
    VideoItem(item)
  } else {
    DefaultItem(item)
  }
}, item => `${item.type}_${item.id}`)

总结

LazyForEach作为鸿蒙长列表渲染的核心方案,其高效性来源于精妙的动态加载+组件复用双重机制。开发者需特别注意:

  1. 键值生成规则必须保证全局唯一性
  2. 数据更新必须通过DataChangeListener规范操作
  3. 结合@Reusable实现组件级复用优化
  4. 合理配置cachedCount平衡性能与内存

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 "今阳说" 接收我的最新文章

相关推荐
二流小码农几秒前
鸿蒙开发:wrapBuilder传递参数
android·ios·harmonyos
别说我什么都不会22 分钟前
鸿蒙(HarmonyOS)性能优化实战-应用性能分析工具CPU Profiler使用指南
性能优化·harmonyos
png41 分钟前
从零开始纯血鸿蒙天气预报-主界面(1)
harmonyos·arkui
城中的雾43 分钟前
鸿蒙开发者必看:如何用一行命令搞定HSP/HAP文件安装?
harmonyos
ljx14000525501 小时前
推荐一个基于Koin, Ktor & Paging等组件的KMM Compose Multiplatform项目
android·kotlin
23zhgjx-zgx1 小时前
OSPF:虚链路
网络·tcp/ip·华为·智能路由器·ensp
lrydnh2 小时前
数据库语句
android·数据库
去看全世界的云2 小时前
【Kotlin】Kotlin基础笔记
android·java·笔记·kotlin
tangweiguo030519872 小时前
Android打造易用的 WiFi 工具类:WifiUtils 封装实践
android·java·wifi
Legendary_0082 小时前
LDR6500:革新手机OTG充电体验的关键芯片
android·智能手机