4、列表与网格: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)
}
}
这里 columnsTemplate 和 rowsTemplate 是核心。它们的语法和 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