核心注意事项总览
类别 |
注意事项 |
详细说明 |
影响程度 |
数据源限制 |
数组不能密封或冻结 |
使用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中必须使用ListItem
Text(item.item.name)
}
})
}
.width('100%')
.height('80%')
// ✅ Grid容器使用
Grid() {
Repeat(this.dataArray)
.each((item: RepeatItem<DataItem>) => {
GridItem() { // Grid中必须使用GridItem
Text(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. 懒加载配置注意事项
复制代码
复制代码
// ✅ 基本懒加载配置
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>) { // 必须接收完整RepeatItem
ListItem() {
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组件的主要使用注意事项,遵循这些规范可以避免常见的陷阱并优化应用性能。