核心注意事项总览
类别 | 注意事项 | 详细说明 | 影响程度 |
---|
数据源限制 | 数组不能密封或冻结 | 使用Object.seal() 或Object.freeze() 会导致Repeat功能失效 | ⚠️ 严重 |
| 必须使用可观察数据 | 数据源需用@Local 、@ObservedV2 等装饰器装饰 | ⚠️ 严重 |
容器限制 | 必须在滚动容器内使用 | 仅支持List、Grid、Swiper、WaterFlow、ListItemGroup | ⚠️ 严重 |
| 容器内只能有一个Repeat | 不能与ForEach、LazyForEach或其他Repeat混用 | ⚠️ 严重 |
装饰器兼容 | 不支持V1装饰器 | 必须使用V2装饰器(@ComponentV2 、@Local 等) | ⚠️ 严重 |
子组件规范 | 子组件类型必须匹配容器 | List中必须用ListItem,Grid中必须用GridItem | ⚠️ 严重 |
懒加载配置 | virtualScroll配置要点 | totalCount、onTotalCount、onLazyLoading的正确使用 | 🔧 重要 |
键值生成 | key()函数必须稳定唯一 | 相同数据必须返回相同key,避免使用index作为key | 🔧 重要 |
模板复用 | templateId必须正确配置 | 相同templateId的组件才能互相复用 | 🔧 重要 |
性能优化 | cachedCount合理设置 | 缓存池大小影响内存和性能平衡 | ⚡ 中等 |
特殊场景 | 与@Builder混用规范 | 必须传递完整的RepeatItem对象 | ⚡ 中等 |
详细注意事项说明
1. 数据源相关注意事项
1.1 数组状态管理
// ✅ 正确用法
@ObservedV2
class DataItem {@Trace id: string;@Trace name: string;constructor(id: string, name: string) {this.id = id;this.name = name;}
}@ComponentV2
struct MyComponent {@Local dataArray: DataItem[] = []; // 必须使用@Local装饰aboutToAppear() {// 初始化数据for (let i = 0; i < 100; i++) {this.dataArray.push(new DataItem(`id_${i}`, `Item ${i}`));}}
}// ❌ 错误用法
@Component
struct WrongComponent {dataArray: any[] = []; // 未使用V2装饰器frozenArray: any[] = Object.freeze([]); // 数组被冻结
}
1.2 数据操作规范
// ✅ 正确的数据更新
updateData(index: number, newData: DataItem) {// 直接修改数组元素this.dataArray[index] = newData;
}addData(item: DataItem) {// 使用标准数组方法this.dataArray.push(item);
}removeData(index: number) {this.dataArray.splice(index, 1);
}// ❌ 错误的数据操作
wrongUpdate() {// 在onLazyLoading外使用非常规操作this.dataArray = [...this.dataArray]; // 创建新数组可能丢失观察能力Object.freeze(this.dataArray); // 冻结数组
}
2. 容器和布局限制
2.1 容器使用规范
// ✅ 正确的容器使用
List({ space: 10 }) {Repeat(this.dataArray).each((item: RepeatItem<DataItem>) => {ListItem() { // List中必须使用ListItemText(item.item.name)}})
}
.width('100%')
.height('80%')// ✅ Grid容器使用
Grid() {Repeat(this.dataArray).each((item: RepeatItem<DataItem>) => {GridItem() { // Grid中必须使用GridItemText(item.item.name)}})
}// ❌ 错误用法
Column() { // Column不是滚动容器Repeat(this.dataArray) // 会报错.each((item: RepeatItem<DataItem>) => {Text(item.item.name)})
}List() {Repeat(this.dataArray)Repeat(this.otherArray) // 多个Repeat,会报错
}
3. 懒加载配置注意事项
3.1 virtualScroll配置
// ✅ 基本懒加载配置
Repeat(this.dataArray).each((item: RepeatItem<DataItem>) => {ListItem() {Text(item.item.name)}}).virtualScroll({totalCount: this.dataArray.length, // 数据总长度reusable: true // 开启节点复用(默认true)})// ✅ 动态数据长度配置
Repeat(this.dataArray).virtualScroll({onTotalCount: () => {// 动态计算数据长度return this.expectedTotalCount;},onLazyLoading: (index: number) => {// 懒加载数据if (!this.dataArray[index]) {this.dataArray[index] = this.loadData(index);}}})// ❌ 错误的懒加载使用
Repeat(this.dataArray).virtualScroll({onLazyLoading: (index: number) => {// 错误:使用非[]操作符this.dataArray.push(new DataItem()); // 应该用this.dataArray[index] = ...// 错误:高耗时操作this.expensiveOperation(index);}})
3.2 onLazyLoading特殊要求
// ✅ 正确的onLazyLoading实现
onLazyLoading: (index: number) => {// 必须使用数组索引赋值this.dataArray[index] = this.createPlaceholderData(index);// 异步加载实际数据setTimeout(() => {this.loadRealDataAsync(index).then(realData => {this.dataArray[index] = realData;});}, 0);
}// ❌ 禁止的操作
onLazyLoading: (index: number) => {// 禁止:使用push、splice等this.dataArray.push(new DataItem());// 禁止:修改其他索引数据this.dataArray[index + 1] = someData;// 禁止:高耗时同步操作const data = this.expensiveSyncLoad(index);
}
4. 键值生成关键要点
4.1 key()函数规范
// ✅ 稳定的键值生成
Repeat(this.dataArray).key((item: DataItem, index: number) => {return item.id; // 使用数据的唯一标识})// ✅ 复合键值
.key((item: DataItem, index: number) => {return `${item.type}_${item.id}`; // 类型+ID确保唯一性
})// ❌ 不稳定的键值(性能问题)
.key((item: DataItem, index: number) => {return index.toString(); // 索引变化会导致重新渲染return Math.random().toString(); // 每次不同,完全错误
})// ❌ 非唯一键值(运行时错误)
.key((item: DataItem, index: number) => {return "same_key_for_all"; // 所有项相同键值
})
5. 模板和复用配置
5.1 模板使用规范
// ✅ 多模板配置
Repeat(this.dataArray).templateId((item: DataItem, index: number) => {return item.type; // 根据数据类型返回模板ID}).template('typeA', (ri: RepeatItem<DataItem>) => {ListItem() {TypeAComponent({ data: ri.item })}}).template('typeB', (ri: RepeatItem<DataItem>) => {ListItem() {TypeBComponent({ data: ri.item })}}).each((ri: RepeatItem<DataItem>) => { // 默认模板ListItem() {DefaultComponent({ data: ri.item })}})// ✅ 缓存配置优化
.template('typeA', (ri: RepeatItem<DataItem>) => {ListItem() {TypeAComponent({ data: ri.item })}
}, { cachedCount: 10 }) // 设置模板缓存数量
5.2 复用功能配置
// ✅ 复用功能配置(API 18+)
Repeat(this.dataArray).virtualScroll({totalCount: this.dataArray.length,reusable: true // 默认true,可设置为false禁用})// ✅ 与@ReusableV2配合使用
Repeat(this.dataArray).virtualScroll({ reusable: false }) // 关闭Repeat复用.each((ri: RepeatItem<DataItem>) => {ListItem() {ReusableComponent({ data: ri.item }) // 使用@ReusableV2组件}})
6. 与@Builder混用规范
6.1 正确的参数传递
// ✅ 传递完整RepeatItem
@Builder
function itemBuilder(ri: RepeatItem<DataItem>) { // 必须接收完整RepeatItemListItem() {Text(`Item: ${ri.item.name} at index ${ri.index}`)}
}Repeat(this.dataArray).each((ri: RepeatItem<DataItem>) => {this.itemBuilder(ri) // 传递完整对象})// ❌ 错误的值传递
@Builder
function wrongBuilder(item: DataItem, index: number) { // 只传递值ListItem() {Text(`Item: ${item.name}`)}
}Repeat(this.dataArray).each((ri: RepeatItem<DataItem>) => {this.wrongBuilder(ri.item, ri.index) // 会导致渲染异常})
7. 性能优化注意事项
7.1 缓存策略配置
// ✅ 合理的缓存配置
List() {Repeat(this.dataArray).each((ri: RepeatItem<DataItem>) => {ListItem() {Text(ri.item.name)}})
}
.cachedCount(3) // 容器预加载数量
.scrollBar(BarState.Off) // 关闭滚动条提升性能// ✅ 模板缓存优化
.template('default', (ri: RepeatItem<DataItem>) => {ListItem() {ComplexComponent({ data: ri.item })}
}, { cachedCount: 5 }) // 复杂组件适当增加缓存// ❌ 过度缓存(内存问题)
.template('simple', (ri: RepeatItem<DataItem>) => {ListItem() {Text(ri.item.name) // 简单组件不需要大缓存}
}, { cachedCount: 50 }) // 缓存过多浪费内存
8. 特殊场景处理
8.1 无限滚动实现
// ✅ 无限滚动配置
Repeat(this.dataArray).virtualScroll({onTotalCount: () => {return this.dataArray.length + 1; // 比实际长度多1},onLazyLoading: (index: number) => {if (index >= this.dataArray.length) {// 加载更多数据this.loadMoreData().then(newData => {this.dataArray.push(...newData);});}}})// ⚠️ 注意事项
// 1. 必须提供初始数据
// 2. 设置cachedCount > 0
// 3. 避免与Swiper-Loop模式同时使用
// 4. 监控内存使用,防止内存泄漏
8.2 拖拽排序(API 19+)
// ✅ 拖拽排序配置
Repeat(this.dataArray).onMove((from: number, to: number) => {// 移动数据,保持键值不变const [movedItem] = this.dataArray.splice(from, 1);this.dataArray.splice(to, 0, movedItem);}).each((ri: RepeatItem<DataItem>) => {ListItem() {Text(ri.item.name)}})// ⚠️ 注意事项
// 1. 只能在List容器中使用
// 2. 每个迭代必须生成ListItem
// 3. 拖拽过程中不能修改数据源
// 4. 移动后保持键值不变
常见错误排查表
错误现象 | 可能原因 | 解决方案 |
---|
渲染异常/空白 | 数组被密封或冻结 | 检查是否使用了Object.freeze()或Object.seal() |
数据不更新 | 未使用V2装饰器 | 确保使用@ComponentV2和@Local等V2装饰器 |
性能卡顿 | 使用index作为key | 改为使用数据唯一标识作为key |
懒加载不触发 | onLazyLoading配置错误 | 检查索引赋值语法和异步操作 |
模板显示错误 | templateId配置冲突 | 确保templateId唯一且对应模板正确定义 |
@Builder渲染异常 | 参数传递方式错误 | 传递完整RepeatItem对象而非单独值 |
这份总结涵盖了ArkUI V2中Repeat组件的主要使用注意事项,遵循这些规范可以避免常见的陷阱并优化应用性能。