一、背景
之前鸿蒙开发项目中主要使用V1装饰器,在循环渲染场景主要使用 forEach 或 lazyForeach,但这两个在动态数据处理、性能优化等方面存在局限。Repeat作为V2装饰器专属的循环渲染方案,专门解决循环渲染的痛点,是适配新开发模式的核心能力之一,特此梳理下用法及优势
二、Repeat是什么?
2.1、概念
Repeat是鸿蒙ArkUI中用于重复渲染相同组件结构的循环控制组件,它通过指定重复次数来生成多个相同的组件实例,特别适合渲染结构相同但重复展示的UI元素
2.2、使用限制
1、必须在滚动类容器中使用,如List、ListItemGroup、Grid、Swiper、WaterFlow,且必须开启
virtualScroll,非滚动容器下无懒加载效果2、仅兼容V2装饰器模式(@ComponentV2),不支持V1组件
3、数据源需用 @Local/@State 等响应式装饰器修饰,否则数据更新无法触发差异化渲染
4、不支持动画效果
5、滚动容器组件内只能包含一个Repeat,以List为例,不建议同时包含ListItem、ForEach、LazyForEach,不建议同时包含多个Repeat。
6、当Repeat与自定义组件或@Builder函数混用时,必须将Repeat类型整体进行传承,组件才能监听到数据变化
2.3、循环渲染说明:
当页面首次渲染时,Repeat根据当前的有效加载范围(屏幕可视区域+预加载区域)按需创建子组件。如下图所示:

2.4、节点更新/复用能力
节点种类:节点创建、节点更新、节点复用、节点销毁
节点更新:节点不销毁,状态变量驱动节点属性更新
节点复用:旧节点不销毁,存储在空闲节点缓存池;需要创建新节点时,直接从缓存池中获取可复用的旧节点,并做相应的节点属性更新。
当滚动容器组件滑动/数组改变时,Repeat将失效的子组件节点(离开有效加载范围)加入空闲节点缓存池中,即断开组件节点与页面组件树的连接但不销毁节点。在需要生成新的组件时,对缓存池里的组件节点进行复用。
2.4.1、执行案例:
背景
定义长度为20的数组,数组前5项的template type为aa,渲染浅蓝色组件,其余项为bb,渲染橙色组件。aa缓存池容量为3,bb缓存池容量为4。容器组件的预加载区域大小为2。为了便于理解,在aa和bb缓存池中分别加入一个和两个空闲节点。
首次渲染
列表的节点状态如下图所示(template type在图中简写为ttype)。

滑动场景
将屏幕向下滑动一个节点的距离,Repeat会复用缓存池中的节点。
1)index=10的节点进入有效加载范围,计算出其template type为bb。由于bb缓存池非空,Repeat会从bb缓存池中取出一个空闲节点进行复用,更新其节点属性(数据item和索引index),该子组件中涉及数据item和索引index的其他孙子组件会根据状态管理V2的规则做同步更新。
2)index=0的节点滑出了有效加载范围。当UI主线程空闲时,会检查aa缓存池是否已满,此时aa缓存池未满,将该节点加入到对应的缓存池中。
3)其余节点仍在有效加载范围,均只更新索引index。如果对应template type的缓存池已满,Repeat会在UI主线程空闲时销毁掉多余的节点。

数据更新场景
在上一小节的基础上做如下的数组更新操作,删除index=4的节点,修改节点数据07为new。
1)删除index=4的节点后,节点05前移。根据template type的计算规则,新的05节点的template type变为aa,直接复用旧的04节点,更新数据item和索引index,并且将旧的05节点加入bb缓存池。
2)后面的列表节点前移,新进入有效加载区域的节点11会复用bb缓存池中的空闲节点,其他节点均只更新索引index。
3)对于节点数据从07变为new的情况,页面监听到数据源变化将会触发重新渲染。Repeat数据更新触发重新渲染的逻辑是比较当前索引处节点数据item是否变化,以此判断是否进行UI刷新,仅改变键值不改变item的情况不会触发刷新。

2.4.2 key 值作用
- 核心作用:精准标识数据项,避免数据更新时组件误重建,保障节点复用逻辑正确;
- 选型建议:优先使用数据唯一标识(如 id),禁止用索引(索引变化会导致 key 失效,破坏复用)
三、为什么选择Repeat
核心:用最简单的方式实现最高效的重复渲染
性能更优:仅刷新变化节点,对比 forEach 全量重渲染,减少性能开销。
场景适配强:支持嵌套循环、条件渲染,语法简洁,降低复杂数据渲染成本。
四、Repeat与 ForEach、LazyForEach 的区别
| 特性 | Repeat(V2 专属) | forEach(通用) | LazyForEach(通用) |
|---|---|---|---|
| 数据监听与更新 | 自动监听状态变量,触发差异化渲染 | 无监听,数据更新全量重渲染 | 需实现 IDataSource,手动管理更新 |
| 节点复用能力 | 增强复用,提升滑动 / 更新性能 | 无复用,销毁重建组件 | 支持懒加载复用,侧重初始渲染优化 |
| 模板渲染支持 | 支持多模板,按自定义类型渲染 | 仅单模板,统一渲染样式 | 仅单模板,无自定义类型适配 |
| 核心依赖 / 复杂度 | 依赖 V2 装饰器,低开发复杂度 | 无依赖,极简语法 | 需实现接口,开发复杂度较高 |
五、具体实现
5.1、前置条件
V2装饰器开发模式
5.2、基础用法(简单列表渲染)
.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 }) // 边框
}
}
}
5.3、复杂用法(渲染多种子组件)
在同一个数据源中渲染多种子组件,每个数据项会根据.templateId()得到template type,从而渲染type对应的.template()中的子组件。
- .each()等价于template type为空字符串的.template()。
- 当多个template type相同时(包括template type为空字符串),Repeat仅生效最新定义的.each()或.template()。
- 如果.templateId()缺省,或templateId()计算得到的template type不存在,则template type取默认值空字符串。
- 只有相同template type的节点可以互相复用。
// 在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 }) // 边框
}
}
}

41

被折叠的 条评论
为什么被折叠?



