🎯 V2: 浅入Repeat:子组件复用

⭐⭐⭐⭐⭐

📌 见解

1️⃣ Repeat常在滚动类容器组件内使用,List、ListItemGroup、Grid、Swiper以及WaterFlow组件支持Repeat懒加载场景

2️⃣ Repeat当前不支持动画效果,与@Builder组合使用时候需要将RepeatItem类型整体进行传参,才是双向绑定刷新❗❗❗

3️⃣ Repeat组件默认开启节点复用功能。从API version 18开始,可以自定义配置VirtualScrollOptions,以及缓存自定义组件冻结功能, Repeat拖拽排序特性从API version 19开始支持❗❗❗

4️⃣ Repeat的.key()属性为每个子组件生成一个键值。用于对标记的组件做curd的操作,当.key()缺省时,Repeat会生成新的随机键值

5️⃣ template与templateId主要用于实现动态多模板渲染,严格区分组件复用池,匹配不到时候默认使用each模版

⚠️ 使用场景

维度 Repeat LazyForEach ForEach
数据量 大/中数据集 (需开懒加载)超大数据集 小数据集(≤100)
节点复用 ✅ 支持 ❌ 不支持 ❌ 不支持
懒加载 ✅ 通过virtualScroll开启 ✅ 内置支持 ❌ 不支持
模板多样性 ✅ 多模板渲染 ❌ 单一模板 ❌ 单一模板
数据更新监听 ✅ 自动监听状态变量 ❌ 需手动实现IDataSource ✅ 自动监听数组变化
适用场景 动态数据、需复用节点/多模板 流式数据、超长列表 静态数据、快速交互

🧩 拆解

@ObservedV2
class mockViewModel {
  @Trace str: string = ''
  num: number = 0
}

@Entry
@ComponentV2
struct RepeatCase {
  @Local mockList: Array<mockViewModel> = []
  @Local mockListLength: number = this.mockList.length
  private scroller: Scroller = new Scroller()
  private start: number = 1

  aboutToAppear(): void {
    for (let i = 0; i < 8; i++) {
      this.mockList.push({ str: `mock${i}`, num: i })
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('添加')
        .padding(20)
        .fontColor(Color.White)
        .backgroundColor(Color.Green)
        .borderRadius(4)
        .onClick(() => {
          const addLength = this.mockList.length + 1
          this.mockList.push({ str: `mock${addLength}`, num: addLength })

          // TODO: 屏幕外的列表数据发生变化时,保证滚动条位置不变
          const rect = this.scroller.getItemRect(this.start) // 获取子组件的大小位置
          this.scroller.scrollToIndex(this.start + 1) // 滑动到指定index
          this.scroller.scrollBy(0, -rect.y) // 滑动指定距离
        })

      List({ space: 10, scroller: this.scroller }) {
        Repeat(this.mockList)
          .each((obj: RepeatItem<mockViewModel>) => {
            ListItem() {
              RepeatItemCase({
                item: obj.item, delItem: () => {
                  this.mockList.splice(obj.index, 1)
                }
              })
            }
          })
          .key(item => item.str)
          .virtualScroll({ totalCount: this.mockList.length }) // 默认加载所有组件
          // TODO: API 19+才可使用
          // .virtualScroll({
          //   // 期望的数据源总长度为1000。
          //   onTotalCount: () => { return 1000; },
          //   // 实现数据懒加载。
          //   onLazyLoading: (index: number) => { this.mockList[index] = this.mockList[index]; }
          // })
      }
      .scrollBar(BarState.Off)
      .width('100%')
      .padding({ bottom: 100 })
      .onScrollIndex((start, end) => {
        this.start = start
      })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Start)
    .padding({ left: 5, right: 5 })
  }
}

@ComponentV2
struct RepeatItemCase {
  @Require @Param item: mockViewModel
  @Event delItem: () => void = () => {}

  build() {
    Row() {
      Text(this.item.str)

      Blank()

      Text('删除')
        .padding(5)
        .fontColor(Color.White)
        .backgroundColor(Color.Red)
        .borderRadius(4)
        .onClick(() => {
          this.delItem()
          this.getUIContext().getPromptAction().showToast({ message: `删除了${this.item.num}项` })
        })
    }
    .width('100%')
    .height(100)
    .borderWidth(1)
    .borderColor(Color.Black)
    .borderRadius(10)
    .padding({ left: 20, right: 20 })
  }
}

📝 Repeat使用方式优于Foreach和LazyForEach,需要注意的是,数据精准懒加载(当数据源总长度较长,或数据项加载耗时较长时,动态加载长列表数据,例如:用户滑动到列表第500项时,触发加载该位置的数据。)需要在api19+才能使用

🌸🌼🌺

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐