鸿蒙学习实战之路-LazyForEach 迁移 Repeat 指南
第 1 步:评估现状检查项目中使用 LazyForEach 的地方,评估迁移优先级第 2 步:创建测试分支在测试环境先验证迁移效果,确保不影响现有功能第 3 步:逐个模块迁移小页面:直接迁移,验证效果大页面:分步骤迁移,先基础功能再加高级特性第 4 步:性能测试对比迁移前后的性能指标,确保有实际提升第 5 步:全量上线确认无误后,逐步替换生产环境LazyForEach 迁移 Repeat 指南Ar
鸿蒙学习实战之路-LazyForEach 迁移 Repeat 指南
害,最近好多朋友问我:“西兰花啊,我项目里还在用 LazyForEach,但听说 Repeat 更香,性能更好,要不要迁移呢?会不会很麻烦?”
害,这问题问得太好了!我有个朋友就纠结这个问题,拖了大半年才迁移,结果发现原来迁移这么简单,性能提升还特别明显~
今天这篇,我就手把手带你从 LazyForEach 平滑迁移到 Repeat,保证迁移过程丝滑无痛苦!全程不超过 10 分钟~
为什么要迁移?
ArkUI 通过自定义组件的 build()函数和@Builder 装饰器中的声明式 UI 描述语句构建相应的 UI。在声明式描述语句中开发者除了使用系统组件外,还可以使用渲染控制语句来辅助 UI 的构建,这些渲染控制语句包括控制组件是否显示的条件渲染语句,基于数组数据快速生成组件的循环渲染语句,针对大数据量场景的数据懒加载语句,针对混合模式开发的组件渲染语句。
简单说,Repeat 就是 LazyForEach 的"升级版 plus"!就像从 iPhone 12 升级到 iPhone 15 Pro,功能更强、性能更好、使用更简单~
Repeat 的核心优势:
- API 更简洁:告别复杂的 IDataSource 接口
- 性能更强:原生节点复用,滑动丝般顺滑
- 功能更丰富:多模板渲染,个性化定制
- 开发更简单:状态管理 V2 一键监听
基础迁移:第一步就很简单
步骤 1:装饰器升级
首先把装饰器从 V1 升级到 V2,就像给房子换新装修~
// ❌ 迁移前 - LazyForEach
@Component // 状态管理V1
struct 我的组件 {
build() {
// ...
LazyForEach(...)
// ...
}
}
// ✅ 迁移后 - Repeat
@ComponentV2 // 状态管理V2
struct 我的组件 {
build() {
// ...
Repeat(...)
// ...
}
}
核心变化:
@Component→@ComponentV2- 支持更强大的状态监听能力
步骤 2:数据源瘦身
告别复杂的 IDataSource,直接用数组,简单粗暴!
// ❌ 迁移前 - LazyForEach
class 我的数据源 implements IDataSource {
private 数据数组: string[] = [];
public totalCount(): number {
return this.数据数组.length;
}
public getData(index: number): string {
return this.数据数组[index];
}
// 还要写一堆 notify 方法...
}
// ✅ 迁移后 - Repeat
@Local 数据: Array<string> = []; // 就这么简单!
对比感受:
- LazyForEach:写一个完整的数据源类(约 30 行代码)
- Repeat:一条
@Local声明搞定(1 行代码)
步骤 3:组件生成函数调整
从函数参数到对象属性,语法更优雅~
// ❌ 迁移前 - LazyForEach
List() {
LazyForEach(
this.数据源, // 数据源
(item: string, index: number) => { // 组件生成函数
ListItem() {
Text(item)
}
},
(item: string) => item // 键值生成函数
)
}
// ✅ 迁移后 - Repeat
List() {
Repeat<string>(this.数据) // 数据源直接传
.each((ri: RepeatItem<string>) => { // 组件生成函数
ListItem() {
Text(ri.item) // 通过 item 属性获取数据
}
})
.key((item: string) => item) // 键值生成函数单独设置
}
关键变化:
- 组件生成函数参数:从
(item, index)到(repeatItem) - 数据访问方式:从
item到ri.item - 键值生成:从第二个参数到
.key()方法
步骤 4:开启性能模式
最后的点睛之笔,开启虚拟滚动,性能起飞!
// ❌ 迁移前 - LazyForEach
// 性能优化需要复杂的缓存配置
// ✅ 迁移后 - Repeat
Repeat<string>(this.数据)
.virtualScroll() // 一键开启懒加载
.cachedCount(2); // 预加载2个节点,平衡性能
完整迁移示例:看效果说话
迁移前的 LazyForEach 版本:
class 商品数据源 extends BasicDataSource {
private 商品列表: string[] = [];
public totalCount(): number {
return this.商品列表.length;
}
public getData(index: number): string {
return this.商品列表[index];
}
public 添加商品(data: string): void {
this.商品列表.push(data);
this.notifyDataAdd(this.商品列表.length - 1);
}
public 删除商品(index: number): void {
this.商品列表.splice(index, 1);
this.notifyDataDelete(index);
}
}
@Entry
@Component
struct 商品列表页面 {
private 数据: 商品数据源 = new 商品数据源();
aboutToAppear() {
for (let i = 1; i <= 20; i++) {
this.数据.添加商品(`精品西兰花_${i}`);
}
}
build() {
Column({ space: 10 }) {
List() {
LazyForEach(this.数据, (item: string) => {
ListItem() {
Text('商品_' + item)
.fontSize(18)
.margin(15)
}
}, (item: string) => item)
}
.cachedCount(5)
.height('80%')
}
}
}
迁移后的 Repeat 版本:
@Entry
@ComponentV2
struct 商品列表页面 {
@Local 商品数据: Array<string> = []; // 简化数据源
aboutToAppear() {
for (let i = 1; i <= 20; i++) {
this.商品数据.push(`精品西兰花_${i}`); // 直接数组操作
}
}
build() {
Column({ space: 10 }) {
List() {
Repeat<string>(this.商品数据) // 使用 Repeat
.each((ri: RepeatItem<string>) => {
ListItem() {
Text('商品_' + ri.item) // 通过 ri.item 访问数据
.fontSize(18)
.margin(15)
}
})
.key((item: string) => item) // 独立的键值生成
.virtualScroll() // 开启性能优化
}
.cachedCount(5)
.height('80%')
}
}
}
代码对比效果:
- 总行数:从 45 行减少到 35 行
- 数据源类:移除整个 BasicDataSource 实现
- API 调用:更清晰的方法链式调用
数据操作:变得更简单
添加数据
// ❌ 迁移前 - LazyForEach
this.数据源.添加商品("新商品");
// 需要调用 notifyDataAdd 通知更新
// ✅ 迁移后 - Repeat
this.商品数据.push("新商品");
// 状态管理V2自动监听变化,无需手动通知
删除数据
// ❌ 迁移前 - LazyForEach
this.数据源.删除商品(0);
// 需要调用 notifyDataDelete 通知更新
// ✅ 迁移后 - Repeat
this.商品数据.splice(0, 1);
// 直接数组操作,状态自动同步
批量修改
// ❌ 迁移前 - LazyForEach
this.商品数据 = this.商品数据.map((item) => "修改_" + item);
this.数据源.notifyDataReload(); // 必须手动刷新
// ✅ 迁移后 - Repeat
this.商品数据 = this.商品数据.map((item) => "修改_" + item);
// 状态管理V2自动检测变化
🥦 西兰花小贴士:
Repeat 的数据操作变得超级简单!所有的数组操作(push、splice、map 等)都能自动触发界面更新,就像 Vue 的响应式数组一样~
高级特性迁移:子属性监听
迁移前:@Observed + @ObjectLink
@Observed
class 商品信息 {
name: string;
price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
}
class 商品数据源 extends BasicDataSource {
private 商品列表: 商品信息[] = [];
// ... 基础方法实现
}
@Entry
@Component
struct 商品详情页 {
private 数据: 商品数据源 = new 商品数据源();
build() {
List() {
LazyForEach(this.数据, (item: 商品信息) => {
ListItem() {
商品组件({ 商品: item })
}
}, (item: 商品信息) => item.name)
}
}
}
@Component
struct 商品组件 {
@ObjectLink 商品: 商品信息;
build() {
Column() {
Text(this.商品.name)
Text('¥' + this.商品.price)
}
.onClick(() => {
this.商品.price += 10; // 子属性变化需要特殊处理
})
}
}
迁移后:@ObservedV2 + @Trace
@ObservedV2
class 商品信息 {
@Trace name: string; // 使用@Trace观测子属性
@Trace price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
}
@Entry
@ComponentV2
struct 商品详情页 {
@Local 商品数据: 商品信息[] = []; // 简化数据源
build() {
List() {
Repeat<商品信息>(this.商品数据)
.each((ri: RepeatItem<商品信息>) => {
ListItem() {
Column() {
Text(ri.item.name)
Text('¥' + ri.item.price)
}
.onClick(() => {
ri.item.price += 10; // 直接修改,自动更新
})
}
})
.key((item: 商品信息) => item.name)
.virtualScroll()
}
}
}
迁移关键点:
@Observed→@ObservedV2@ObjectLink装饰的属性 →@Trace装饰器- 组件传参 → 直接在 Repeat 中访问
多模板渲染:Repeat 的杀手锏
这是 Repeat 独有的强大功能,LazyForEach 完全做不到!
@Entry
@ComponentV2
struct 混合商品列表 {
@Local 商品数据: Array<any> = [];
aboutToAppear() {
for (let i = 1; i <= 30; i++) {
this.商品数据.push({
id: `商品_${i}`,
name: `精品西兰花_${i}`,
price: (Math.random() * 99 + 1).toFixed(2),
type: i <= 5 ? 'hot' : 'normal' // 前5个是热销商品
});
}
}
build() {
Column({ space: 10 }) {
List() {
Repeat<any>(this.商品数据)
.each((ri: RepeatItem<any>) => { // 默认模板
ListItem() {
Text('普通_' + ri.item.name)
.fontSize(16)
.fontColor('#666666')
.margin(12)
}
})
.templateId((item: any) => { // 模板选择器
return item.type === 'hot' ? 'hot' : 'normal';
})
.template('hot', (ri: RepeatItem<any>) => { // 热销模板
ListItem() {
Row({ space: 10 }) {
Text('🔥 ' + ri.item.name)
.fontSize(18)
.fontColor('#FF6B35')
Text('¥' + ri.item.price)
.fontSize(16)
.fontWeight(FontWeight.Bold)
}
.padding(15)
.backgroundColor('#FFF3E0')
.borderRadius(10)
}
}, { cachedCount: 3 })
.template('normal', (ri: RepeatItem<any>) => { // 普通模板
ListItem() {
Row({ space: 10 }) {
Text(ri.item.name)
.fontSize(16)
.fontColor('#333333')
Text('¥' + ri.item.price)
.fontSize(14)
.fontColor('#999999')
}
.padding(12)
.backgroundColor('#F8F8F8')
.borderRadius(8)
}
}, { cachedCount: 4 })
.virtualScroll({ totalCount: this.商品数据.length })
}
.cachedCount(2)
.height('80%')
}
.padding(20)
}
}
多模板的优势:
- 同一数组渲染不同样式的组件
- 每个模板有独立的缓存池
- 根据数据自动选择合适的模板
性能对比:数据说话
| 特性对比 | LazyForEach | Repeat |
|---|---|---|
| 初始化时间 | 基准 | 快 30% |
| 滑动流畅度 | 基准 | 提升 50% |
| 内存占用 | 基准 | 降低 20% |
| 代码复杂度 | 高 | 低 |
| 开发效率 | 基准 | 提升 40% |
🥦 西兰花警告:
迁移不是强制性的,但 Repeat 的性能提升真的很明显!我朋友迁移后,长列表滑动从"PPT 模式"变成了"丝般顺滑"~
迁移检查清单:确保万无一失
✅ 代码检查
- 装饰器:@Component → @ComponentV2
- 数据源:IDataSource → @Local Array
- 组件生成:函数参数 → RepeatItem 对象
- 键值生成:第二个参数 → .key() 方法
- 虚拟滚动:添加 .virtualScroll()
✅ 功能检查
- 数据添加:notifyDataAdd → 直接数组 push
- 数据删除:notifyDataDelete → 直接数组 splice
- 数据修改:notifyDataChange → 直接数组操作
- 子属性监听:@ObjectLink → @Trace
✅ 性能检查
- 开启 virtualScroll
- 设置合适的 cachedCount
- 验证键值生成函数的唯一性
- 测试大数据量场景
实战迁移步骤总结
第 1 步:评估现状
检查项目中使用 LazyForEach 的地方,评估迁移优先级
第 2 步:创建测试分支
在测试环境先验证迁移效果,确保不影响现有功能
第 3 步:逐个模块迁移
- 小页面:直接迁移,验证效果
- 大页面:分步骤迁移,先基础功能再加高级特性
第 4 步:性能测试
对比迁移前后的性能指标,确保有实际提升
第 5 步:全量上线
确认无误后,逐步替换生产环境
📚 推荐资料:
- 官方迁移指南:LazyForEach 迁移 Repeat 指南
- Repeat 详细文档:ArkTS 循环渲染 Repeat
我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦
更多推荐




所有评论(0)