HarmonyOS Repeat 可复用的循环渲染

2025-06-24 00:24:42
110次阅读
0个评论

HarmonyOS Repeat 可复用的循环渲染

什么是 Repeat

类似懒加载 LazyForEach,都能提高长列表的渲染性能,都能实现组件的动态渲染和释放,节省内存。区别主要是:

  • Repeat 直接监听状态变量的变化,而 LazyForEach 需要开发者实现IDataSource接口

    Repeat 使用起来要更加简单一些

  • Repeat 还增强了节点复用能力,提高了长列表滑动和数据更新的渲染性能。

    Repeat 存在缓冲池,需要复用节点的时候可以从缓冲池中获取,LazyForEach 是直接重新创建

  • Repeat 增加了渲染模板(template)的能力,在同一个数组中,根据开发者自定义的模板类型(template type)渲染不同的子组件。

    适用于不同结构的组件可以提前把结构定义在模板 template 中,简化 UI 结构

Repeat 也可以看成是 LazyForEach 的升级版。

核心概念

关键字 说明
each 渲染普通节点
templateId 指定要使用的模板的 id
template 模板
key 要循环渲染的节点的唯一标志
virtualScroll 打开懒加载,totalCount 为期望加载的数据长度
onMove 列表项拖拽事件
cachedCount 设置预加载数量和是否开启动画-默认开启

组件复用的基本原理

Repeat 根据当前的有效加载范围(屏幕可视区域+预加载区域)按需创建子组件

image-20250624000205754

组件复用的过程

首次渲染时

image-20250624000418732

  • L1 缓存为 Repeat 有效加载区域
  • L2 缓存为每个循环渲染模板的空闲节点缓存池。

开始滑动列表

将屏幕向右滑动(屏幕内容右移)一个节点的距离,Repeat 将开始复用缓存池中的节点。index=10 的节点进入有效加载范围,计算

出其 template type 为 bb。由于 bb 缓存池非空,Repeat 会从 bb 缓存池中取出一个空闲节点进行复用,更新其节点属性,该子组件中涉

及数据 item 和索引 index 的其他孙子组件会根据 V2 状态管理的规则做同步更新。其他节点仍在有效加载范围,均只更新索引 index。

index=0 的节点滑出了有效加载范围。当 UI 主线程空闲时,会检查 aa 缓存池是否已满,此时 aa 缓存池未满,将该节点加入到对应的缓

存池中。

如果此时对应 template type 的缓存池已满,Repeat 会销毁掉多余的节点。

image-20250624000707881

数据更新

删除 index=4 的节点,修改节点数据 item_7 为 new_7。

首先,删除 index=4 的节点后,失效节点加入 aa 缓存池。后面的列表节点前移,新进入有效加载区域的节点 item_11 会复用 bb 缓存池

中的空闲节点,其他节点均只更新索引 index。

image-20250624000734163

最后,节点 item_5 前移,索引 index 更新为 4。根据 template type 的计算规则,节点 item_5 的 template type 变为 aa,需要从 aa 缓存池中复用空闲节点,并且将旧节点加入 bb 缓存池

image-20250624000807035

基本使用

对于结构固定的组件,可以使用 Repeat 和 each 的简单搭配

// 在List容器组件中使用Repeat
@Entry
@ComponentV2
  // 推荐使用V2装饰器
struct RepeatExample {
  @Local dataArr: Array<string> = []; // 数据源

  aboutToAppear(): void {
    for (let i = 0; i < 50; i++) {
      this.dataArr.push(`data_${i}`); // 模拟动态加载数据
    }
  }

  build() {
    Column() {
      List() {
        Repeat<string>(this.dataArr)
          .each((ri: RepeatItem<string>) => {
            ListItem() {
              Text('each_' + ri.item).fontSize(30)
            }
          })
          .virtualScroll({ totalCount: this.dataArr.length }) // 打开懒加载,totalCount为期望加载的数据长度
      }
      .cachedCount(2) // 容器组件的预加载区域大小
      .height('70%')
      .border({ width: 1 }) // 边框
    }
  }
}

效果

0000000000011111111.20250620155133.75981077787316035971410601430804

搭配 template 使用

  • templateId 指定模板 id

  • template 声明模板

// 在List容器组件中使用Repeat
@Entry
@ComponentV2
  // 推荐使用V2装饰器
struct RepeatExampleWithTemplates {
  @Local dataArr: Array<string> = []; // 数据源

  aboutToAppear(): void {
    for (let i = 0; i < 50; i++) {
      this.dataArr.push(`data_${i}`); // 为数组添加一些数据
    }
  }

  build() {
    Column() {
      List() {
        Repeat<string>(this.dataArr)
          .each((ri: RepeatItem<string>) => { // 默认渲染模板
            ListItem() {
              Text('each_' + ri.item).fontSize(30).fontColor('rgb(161,10,33)') // 文本颜色为红色
            }
          })
          .key((item: string, index: number): string => JSON.stringify(item)) // 键值生成函数
          .virtualScroll({ totalCount: this.dataArr.length }) // 打开懒加载,totalCount为期望加载的数据长度
          .templateId((item: string, index: number): string => { // 根据返回值寻找对应的模板子组件进行渲染
            return index <= 4 ? 'A' : (index <= 10 ? 'B' : ''); // 前5个节点模板为A,接下来的5个为B,其余为默认模板
          })
          .template('A', (ri: RepeatItem<string>) => { // 'A'模板
            ListItem() {
              Text('A_' + ri.item).fontSize(30).fontColor('rgb(23,169,141)') // 文本颜色为绿色
            }
          }, { cachedCount: 3 }) // 'A'模板的缓存列表容量为3
          .template('B', (ri: RepeatItem<string>) => { // 'B'模板
            ListItem() {
              Text('B_' + ri.item).fontSize(30).fontColor('rgb(39,135,217)') // 文本颜色为蓝色
            }
          }, { cachedCount: 4 }) // 'B'模板的缓存列表容量为4
      }
      .cachedCount(2) // 容器组件的预加载区域大小
      .height('70%')
      .border({ width: 1 }) // 边框
    }
  }
}

效果

0000000000011111111.20250620155133.56282241275264687848543582566127

更多使用场景

链接

image-20250624001130167

特别注意

节点更新说明

Repeat 子组件的节点操作分为四种:节点创建、节点更新、节点复用、节点销毁。其中,节点更新和节点复用的区别为:

  • 节点更新:节点不销毁,状态变量驱动节点属性更新。
  • 节点复用:旧节点不销毁,存储在空闲节点缓存池;需要创建新节点时,直接从缓存池中获取可复用的旧节点,并做相应的节点属性更新。

使用限制

使用限制

  • Repeat 必须在滚动类容器组件内使用,仅有ListGridSwiper以及WaterFlow组件支持 Repeat 懒加载场景。

    循环渲染只允许创建一个子组件,子组件应当是允许包含在容器组件中的子组件。例如:Repeat 与List组件配合使用时,子组件必须为ListItem组件。

  • Repeat 不支持 V1 装饰器,混用 V1 装饰器会导致渲染异常。

  • 滚动容器组件内只能包含一个 Repeat。以 List 为例,同时包含 ListItem、ForEach、LazyForEach 的场景是不推荐的;同时包含多个 Repeat 也是不推荐的。

  • 当 Repeat 与自定义组件或[@Builder](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder)函数混用时,必须将 RepeatItem 类型整体进行传参,组件才能监听到数据变化。详见Repeat 与@Builder 混用

关于我们

关于青蓝逐码组织

如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯甚至你想要做出一款属于自己的应用!欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。

image-20250622200325374

收藏00

登录 后评论。没有帐号? 注册 一个。