ArkTS懒加载LazyForEach的基本使用

在 ArkTS 的开发中,如果你要渲染一个很长的列表,比如商品列表、评论列表或者朋友圈动态,用传统的循环结构(比如 ForEach)很容易导致性能问题,尤其是加载慢、卡顿甚至内存暴涨。

这时候就要用到 懒加载渲染组件——LazyForEach

LazyForEach 是 ArkTS 提供的一种 延迟渲染列表项的方式。只有当列表项真正要被显示在屏幕上时,相关组件才会被创建和渲染,从而节省内存和提升性能。

可以把它理解成 ArkTS 中的“虚拟滚动列表”。

LazyForEach的使用限制

  • LazyForEach必须在容器组件内使用仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。支持数据懒加载的父组件根据自身及子组件的高度或宽度计算可视区域内需布局的子节点数量,高度或宽度的缺失会导致部分场景懒加载失效

catchdCount

List设置cachedCount后,显示区域外上下各会预加载并布局cachedCount行ListItem。

  • LazyForEach依赖生成的键值判断是否刷新子组件,键值不变则不触发刷新。
  • 容器组件内只能包含一个LazyForEach。以List为例,不推荐同时包含ListItem、ForEach、LazyForEach。也不推荐同时包含多个LazyForEach。
  • LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件
  • 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
  • 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
  • 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
  • LazyForEach必须使用DataChangeListener对象进行更新。重新赋值第一个参数dataSource会导致异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新
  • 为了高性能渲染,使用DataChangeListener对象的onDataChange方法更新UI时,需要生成不同于原来的键值来触发组件刷新。

LazyForEach键值生成规则

LazyForEach提供了参数keyGenerator,开发者可以使用该函数生成自定义键值。如果未定义keyGenerator函数,ArkUI框架将使用默认的键值生成函数:(item: Object, index: number) => { return viewId + ‘-’ + index.toString(); }。viewId在编译器转换过程中生成,同一个LazyForEach组件内的viewId一致。

基本语法

LazyForEach(
    dataSource: IDataSource,             // 需要进行数据迭代的数据源
    itemGenerator: (item: any, index?: number) => void,  // 子组件生成函数
    keyGenerator?: (item: any, index?: number) => string // 键值生成函数
): void

  • dataSource:IDataSource

    LazyForEach数据源,需要开发者实现相关接口

  • itemGenerator:(item: any, index: number) => void

    子组件生成函数,为数组中的每一个数据项创建一个子组件。

  • keyGenerator: (item: any, index: number) => string

    键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。修改数据源中的一个数据项若不影响其生成的键值,则对应组件不会被更新,否则此处组件就会被重建更新。keyGenerator参数是可选的,但是,为了使开发框架能够更好地识别数组更改并正确更新组件,建议提供

实现IDataSource接口

interface IDataSource {
    totalCount(): number; // 获得数据总数
    getData(index: number): Object; // 获取索引值对应的数据
    registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
    unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}

DataChangeListener接口

interface DataChangeListener {
    onDataReloaded(): void; // 重新加载数据完成后调用
    onDataAdded(index: number): void; // 添加数据完成后调用
    onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDeleted(index: number): void; // 删除数据完成后调用
    onDataChanged(index: number): void; // 改变数据完成后调用
    onDataAdd(index: number): void; // 添加数据完成后调用
    onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDelete(index: number): void; // 删除数据完成后调用
    onDataChange(index: number): void; // 改变数据完成后调用
}

创建LazyForEach

首次渲染

在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值并创建相应的组件。

局部代码如下:

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

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

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

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

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 0; i <= 20; i++) {
      this.data.pushData(`Hello ${i}`);
    }
  }

  build() {
    List({ space: 3 }) { // 必须在容器组件内
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info(`appear: ${item}`);
              })
          }.margin({ left: 10, right: 10 })
        }
      }, (item: string) => item) // 生成唯一键值
    }.cachedCount(5) // 设置显示区域外上下预加载布局
  }
}

渲染结果如图

在这里插入图片描述

非首次渲染

当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新。

局部代码如下:

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

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

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

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

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (let i = 0; i <= 20; i++) {
      this.data.pushData(`Hello ${i}`);
    }
  }

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info(`appear: ${item}`);
              })
          }.margin({ left: 10, right: 10 })
        }
        .onClick(() => {
          // 点击追加子组件
          this.data.pushData(`Hello ${this.data.totalCount()}`);
        })
      }, (item: string) => item)
    }.cachedCount(5)
  }
}

渲染结果如图:
在这里插入图片描述

LazyForEach之前:先实现IDataSource接口

// 实现IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

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

  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);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
    });
  }

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

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
    });
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
      // 写法2:listener.onDatasetChange(
      //         [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);
    });
  }

  notifyDatasetChange(operations: DataOperation[]): void {
    this.listeners.forEach(listener => {
      listener.onDatasetChange(operations);
    });
  }
}
内容概要:本文详细探讨了基于阻尼连续可调减振器(CDC)的半主动悬架系统的控制策略。首先建立了CDC减振器的动力学模型,验证了其阻尼特性,并通过实验确认了模型的准确性。接着,搭建了1/4车辆悬架模型,分析了不同阻尼系数对悬架性能的影响。随后,引入了PID、自适应模糊PID和模糊-PID并联三种控制策略,通过仿真比较它们的性能提升效果。研究表明,模糊-PID并联控制能最优地提升悬架综合性能,在平顺性和稳定性间取得最佳平衡。此外,还深入分析了CDC减振器的特性,优化了控制策略,并进行了系统级验证。 适用人群:从事汽车工程、机械工程及相关领域的研究人员和技术人员,尤其是对车辆悬架系统和控制策略感兴趣的读者。 使用场景及目标:①适用于研究和开发基于CDC减振器的半主动悬架系统的工程师;②帮助理解不同控制策略(如PID、模糊PID、模糊-PID并联)在悬架系统中的应用及其性能差异;③为优化车辆行驶舒适性和稳定性提供理论依据和技术支持。 其他说明:本文不仅提供了详细的数学模型和仿真代码,还通过实验数据验证了模型的准确性。对于希望深入了解CDC减振器工作原理及其控制策略的读者来说,本文是一份极具价值的参考资料。同时,文中还介绍了多种控制策略的具体实现方法及其优缺点,为后续的研究和实际应用提供了有益的借鉴。
<think>嗯,用户想了解鸿蒙ArkTS中的LazyForEach的用法和实现。首先,我需要回忆一下HarmonyOS的文档内容,尤其是关于ForEach和LazyForEach的区别。记得LazyForEach是用于处理大数据集的懒加载,这样可以优化性能。根据系统级指令,我需要确保所有数学公式用$符号包裹,但这里可能用不上数学公式,不过代码块需要用正确的格式。 用户提到的引用[1]提到ForEach和LazyForEach生成唯一键值并创建组件,这一点很重要,应该包含在回答中。接下来,我需要分点说明LazyForEach的用法、实现机制和注意事项。可能需要比较LazyForEach和普通ForEach的区别,强调懒加载的优势,比如内存优化和滚动性能。 然后,要确保代码示例正确,使用ArkTS的语法。例如,展示如何构建LazyForEach组件,包括数据源、键值生成函数和子组件生成函数。另外,需要注意唯一键值的重要性,避免重复导致渲染问题。 用户可能还想知道什么时候该用LazyForEach,比如数据量大的时候,或者需要动态加载的情况。同时,注意事项部分要提到数据源必须可迭代,键值唯一性,以及如何结合滚动容器使用。 最后,生成相关问题的时候,要围绕性能优化、与ForEach的区别、键值生成规则以及实际应用场景来提问。确保问题能引导用户进一步学习相关内容。 检查一下是否所有行内代码用了反引号,独立代码块用了三个反引号,并且引用了正确的文献。确认回答结构清晰,分点明确,符合用户的要求。没有使用任何markdown,语言口语化,自然流畅。</think>关于鸿蒙ArkTSLazyForEach的用法及实现,以下是技术说明: ### 一、LazyForEach核心特性 1. **懒加载机制**:仅在组件即将进入可视区域时创建对应子组件,支持动态回收不可见区域的组件实例,显著优化长列表内存占用[^1] 2. **键值管理**: - 通过`keyGenerator`函数为每个数组项生成唯一键值 - 使用`Object.keys()`方法自动生成默认键值(当未显式指定时) 3. **数据绑定**:支持响应式数据源更新,当数据变更时自动触发UI刷新 ### 二、基础语法结构 ```typescript LazyForEach( dataSource: IDataSource, // 数据源对象 keyGenerator: (item: any) => string, // 键值生成函数 itemGenerator: (item: any) => void // 子组件生成函数 ) ``` ### 三、典型实现示例 ```typescript // 数据源实现IDataSource接口 class MyDataSource implements IDataSource { private dataArray: string[] = [...Array(1000).keys()].map(i => `Item ${i}`) getData(index: number): any { return this.dataArray[index] } totalCount(): number { return this.dataArray.length } } @Entry @Component struct MyComponent { private data = new MyDataSource() build() { List({ space: 20 }) { LazyForEach( this.data, (item: string) => item, // 使用项目内容作为键值 (item: string) => { ListItem() { Text(item) .fontSize(20) .onClick(() => { console.log(`Clicked ${item}`) }) } } ) } } } ``` ### 四、性能优化要点 1. **键值稳定性**:确保相同数据项始终生成相同键值,避免重复渲染 2. **组件复用**:配合`cachedCount`属性设置缓存组件数量(默认值1),平衡内存与流畅度 3. **数据分页**:建议结合分页加载机制,实现超大数据集的高效处理 ### 五、与ForEach的核心差异 | 特性 | LazyForEach | ForEach | |---------------------|----------------------|---------------| | 渲染方式 | 可视区域动态加载 | 全量立即渲染 | | 内存占用 | O(屏幕可见项数) | O(全部数据项) | | 适用场景 | 长列表/大数据集 | 小型数据集 | | 滚动性能 | 更优 | 可能卡顿 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值