4、列表与网格:List组件基础、ForEach循环渲染、Grid网格布局、LazyForEach懒加载

列表和网格,说白了就是咱们在屏幕上展示一堆数据时最常用的两种姿势。你想想看,无论是聊天记录、商品列表,还是照片墙、应用图标,底层都离不开这几个组件。今天我就带你把这几个家伙彻底搞明白。

核心知识体系

列表与网格核心知识体系 数据展示容器 List 组件 ForEach 循环 Grid 网格 LazyForEach 线性排列 / 可滚动 遍历数组 / 生成组件 二维布局 / 列数控制 按需加载 / 性能优化 核心目标:高效展示数据 + 流畅滚动体验

4.1 List 组件基础

List 组件,说白了就是咱们最熟悉的「列表」。我刚开始接触鸿蒙开发时,第一个练手的项目就是个待办事项列表,所以对这个组件感情挺深。

它的用法其实特别简单。你只需要把数据塞进去,然后告诉它每行长什么样就行。来看个最基础的例子:

@Entry
@Component
struct BasicListDemo {
  private fruits: string[] = ['苹果', '香蕉', '橘子', '葡萄', '西瓜']

  build() {
    List() {
      ForEach(this.fruits, (item: string) => {
        ListItem() {
          Text(item)
            .width('100%')
            .height(60)
            .backgroundColor('#f5f5f5')
            .borderRadius(8)
            .margin({ bottom: 8 })
            .textAlign(TextAlign.Center)
        }
      })
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

嗯,这里要注意几个点:

  • List 本身自带滚动能力,你不需要额外套 Scroll 组件。我见过不少新手在外面又包一层 Scroll,结果滚动冲突了,半天找不出原因。
  • ListItem 是 List 的直接子组件,不能跳过。每个列表项都得用 ListItem 包起来。
  • List 默认是纵向排列,想横向?加个 .listDirection(Axis.Horizontal) 就行。

小技巧: 我个人习惯在 List 上设置 .edgeEffect(EdgeEffect.Spring),这样拉到边界时会有个回弹效果,用户体验会好很多。尤其是做社交类应用时,这个细节挺加分的。

4.2 ForEach 循环渲染

ForEach 是什么?说白了就是个「遍历器」。你给它一个数组,它帮你把每个元素变成对应的 UI 组件。

它的签名长这样:

ForEach(
  arr: any[],           // 数据源
  itemGenerator: (item: any, index?: number) => void,  // 生成组件
  keyGenerator?: (item: any, index?: number) => string // 生成唯一key
)

第三个参数 keyGenerator 很多人会忽略,但我建议你一定要加上。为什么?

我曾经在一个聊天列表里没写 key,结果每次新增消息时,整个列表都重新渲染了。用户那边滑动时明显卡顿。加上 key 之后,框架就能精准地知道「哪条数据对应哪个组件」,只更新变化的部分,性能提升非常明显。

ForEach(this.messageList, 
  (msg: Message) => {
    ListItem() {
      MessageItem({ data: msg })
    }
  },
  (msg: Message) => msg.id  // 用唯一id作为key
)

避坑指南: 我曾经遇到过一个问题——数组里有两个相同的数据,结果 key 重复了,界面直接报错。所以 key 一定要保证唯一性。如果数据本身没有 id,可以用 item + '_' + index 来组合生成。

4.3 Grid 网格布局

Grid 网格布局,说白了就是「表格化排列」。照片墙、应用图标、商品展示……这些场景都离不开它。

Grid 和 List 最大的区别在于:List 是一维的,Grid 是二维的。你可以控制每行显示几列,也可以让每个格子大小不一样。

来看个经典的三列网格:

@Entry
@Component
struct GridDemo {
  private apps: string[] = ['微信', '支付宝', '淘宝', '抖音', '美团', '饿了么']

  build() {
    Grid() {
      ForEach(this.apps, (app: string) => {
        GridItem() {
          Column() {
            Image($r('app.media.icon_placeholder'))
              .width(48)
              .height(48)
            Text(app)
              .fontSize(14)
              .margin({ top: 8 })
          }
          .width('100%')
          .height(100)
          .justifyContent(FlexAlign.Center)
        }
      })
    }
    .columnsTemplate('1fr 1fr 1fr')  // 三列等宽
    .rowsTemplate('1fr 1fr')         // 两行等高
    .columnsGap(16)                  // 列间距
    .rowsGap(16)                     // 行间距
    .padding(16)
    .width('100%')
    .height(300)
  }
}

这里 columnsTemplaterowsTemplate 是核心。它们的语法和 CSS Grid 的 grid-template-columns 很像:

写法 含义 示例
'1fr 1fr 1fr' 三列等宽 每列占 1/3
'100px 1fr 2fr' 混合单位 第一列固定100px,后两列按1:2分配
'repeat(3, 1fr)' 重复写法 等价于 '1fr 1fr 1fr'

个人经验: 做商品列表时,我习惯用 columnsTemplate('1fr 1fr') 两列布局。因为手机屏幕宽度有限,三列的话每个格子太小,图片和文字都挤在一起,用户体验不好。两列刚刚好。

4.4 LazyForEach 懒加载

终于到了重头戏。LazyForEach 是什么?说白了就是「按需加载」。

你想想看,如果你的列表有 10000 条数据,用 ForEach 会怎样?它会一次性创建 10000 个组件。哪怕用户只看到屏幕上的 10 条,剩下的 9990 个组件也在内存里躺着。这性能能好吗?

LazyForEach 就解决了这个问题——它只创建当前可见区域内的组件,滑出去的就销毁,滑回来再重建。内存占用和渲染压力都大大降低。

使用 LazyForEach 需要先实现一个数据源类:

class MyDataSource implements IDataSource {
  private dataArray: string[] = []

  constructor(data: string[]) {
    this.dataArray = data
  }

  totalCount(): number {
    return this.dataArray.length
  }

  getData(index: number): string {
    return this.dataArray[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    // 注册监听器,数据变化时通知框架
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    // 注销监听器
  }
}

@Entry
@Component
struct LazyListDemo {
  private dataSource: MyDataSource = new MyDataSource(
    Array.from({ length: 10000 }, (_, i) => `第 ${i + 1} 条数据`)
  )

  build() {
    List() {
      LazyForEach(this.dataSource, (item: string) => {
        ListItem() {
          Text(item)
            .width('100%')
            .height(50)
            .backgroundColor('#e8f4e8')
            .margin({ bottom: 4 })
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

避坑指南: 我曾经在项目里直接用 LazyForEach 套了一个只有 20 条数据的列表,结果发现性能反而比 ForEach 还差。为什么?因为懒加载本身有开销——它需要计算哪些组件在可见区域、哪些需要销毁。数据量小的时候,这点开销反而成了负担。所以我的建议是:数据量超过 200 条时用 LazyForEach,少于 200 条用 ForEach 就够了

4.5 如何选择?一张表说清楚

场景 推荐方案 原因
数据量 < 200 条 ForEach + List 简单直接,开销小
数据量 > 200 条 LazyForEach + List 按需加载,内存友好
照片墙/应用图标 Grid + ForEach 二维布局,视觉整齐
无限滚动列表 LazyForEach + List 数据不断追加,必须懒加载

嗯,以上就是列表与网格的核心内容。说白了就是三件事:List 管一维、Grid 管二维、LazyForEach 管性能。把这三点吃透了,大部分数据展示场景你都能轻松应对。

核心总结:

  • List + ListItem 是最基础的列表组合
  • ForEach 遍历数组生成组件,记得加 key
  • Grid 用 columnsTemplate 控制列数
  • LazyForEach 是大数据量的性能救星

公众号:蓝海资料掘金营,微信deep3321