【HarmonyOS实战】 List与ForEach:高效列表渲染完全指南

【HarmonyOS实战】 List与ForEach:高效列表渲染完全指南 文章目录前言一、List 的基本用法二、ForEach数据驱动列表2.1 基本语法2.2 key 为什么重要三、项目中的完整列表实现四、Scroll List 的组合4.1 为什么要 Scroll 包 List4.2 scrollable 方向4.3 edgeEffect边缘效果4.4 scrollBar滚动条五、条件渲染空列表处理六、List 的其他常用属性sticky 粘性头部七、LazyForEach大数据量的性能优化总结前言加油站列表是一个典型的列表 UI数据驱动、每项样式相同、可以滚动。HarmonyOS 的列表组件是List配合ForEach实现数据驱动渲染。看起来简单但ForEach的 key 函数、List的各种属性、Scroll和List的组合用法有不少细节值得深挖。这篇文章把这些都讲清楚。项目预览一、List 的基本用法List(){ListItem(){Text(第一项)}ListItem(){Text(第二项)}}List是容器ListItem是每一项的包装。List 里只能放 ListItem如果放其他组件会报错。二、ForEach数据驱动列表2.1 基本语法ForEach(this.stationInfoList,// 数据数组(station:StationData){// 渲染函数每一项的 UIListItem(){Row({space:Constants.SPACE_12}){this.stationInfoCard(station);}.width(Constants.FULL_PERCENT);};},(station:StationData){// key 生成函数重要returnstation.idstation.name;})三个参数数据数组要渲染的数据渲染函数接收单个数据项返回 UIkey 生成函数接收单个数据项返回唯一的 key 字符串2.2 key 为什么重要// key 生成函数(station:StationData){returnstation.idstation.name;// 1中国石化加油站(AA站)}key是框架用来识别哪个 ListItem 对应哪条数据的标识。有 key 的情况正确数据新增一项框架知道已有的 3 项不需要重建只新建第 4 个 ListItem数据顺序变化如排序框架移动已有 ListItem不重建没有 key危险任何数据变化框架可能重建所有 ListItem性能差还可能丢失 ListItem 的内部状态如展开/收起状态key 的要求必须唯一同一列表里不能重复建议用数据 ID而不是数组下标数组下标在数据增删时会变化// ❌ 用数组下标不稳定(item,index)index.toString()// ✅ 用数据唯一标识(station:StationData)station.id// ✅ 也可以组合更安全(station:StationData)station.idstation.name三、项目中的完整列表实现// GasStationPage.ets → bindBuilderBuilderbindBuilder(){Column(){Scroll(){// 外层滚动容器if(this.stationInfoListthis.stationInfoList.length0){List(){ForEach(this.stationInfoList,(station:StationData){ListItem(){Row({space:Constants.SPACE_12}){this.stationInfoCard(station);}.width(Constants.FULL_PERCENT);};},(station:StationData){returnstation.idstation.name;// key 生成});}.width(Constants.FULL_PERCENT);}}.backgroundColor($r(app.color.start_window_background)).borderRadius(Constants.BORDER_RADIUS)// 20px 圆角.margin({left:16,right:16}).opacity(Constants.ONE).scrollable(ScrollDirection.Vertical)// 垂直滚动.edgeEffect(EdgeEffect.Fade)// 边缘渐隐.layoutWeight(Constants.ONE).align(Alignment.Top).height(Constants.MY_BUILDER_HEIGHT)// 300px.width(Constants.FULL_PERCENT).scrollBar(BarState.Off);// 隐藏滚动条}.height(Constants.MY_BUILDER_COLUMN_HEIGHT);// 380px}四、Scroll List 的组合4.1 为什么要 Scroll 包 List正常情况下List自带滚动能力不需要额外的Scroll包裹。但项目里用Scroll包了List原因是Scroll组件提供更多样式控制backgroundColor、borderRadius、opacity、edgeEffect等而这些属性直接设在List上也可以但代码层次上Scroll作为可滚动区域的容器语义更清晰。实际上在这个场景里直接用List设置样式也完全可以// 简化写法效果等同List(){ForEach(...);}.backgroundColor(...).borderRadius(20).height(300).scrollBar(BarState.Off).edgeEffect(EdgeEffect.Fade)4.2 scrollable 方向.scrollable(ScrollDirection.Vertical)// 垂直滚动默认.scrollable(ScrollDirection.Horizontal)// 水平滚动.scrollable(ScrollDirection.None)// 禁止滚动4.3 edgeEffect边缘效果.edgeEffect(EdgeEffect.Fade)// 边缘渐隐靠近边界时内容变透明.edgeEffect(EdgeEffect.Spring)// 弹簧效果拉到边界回弹.edgeEffect(EdgeEffect.None)// 无效果到边界就停EdgeEffect.Fade是推荐的效果视觉上更优雅用户能直观感受到到底了。4.4 scrollBar滚动条.scrollBar(BarState.Off)// 隐藏滚动条.scrollBar(BarState.On)// 始终显示.scrollBar(BarState.Auto)// 滚动时显示停止后隐藏隐藏滚动条让界面更简洁适合内容较少的列表。五、条件渲染空列表处理if(this.stationInfoListthis.stationInfoList.length0){List(){ForEach(...);}}在渲染列表前先判断数据是否存在避免空数组渲染出奇怪的 UI。可以加上空状态的提示if(this.stationInfoListthis.stationInfoList.length0){List(){ForEach(...);}}else{Column(){Text(附近暂无加油站).fontSize(16).fontColor(#999999);}.width(100%).height(200).justifyContent(FlexAlign.Center);}六、List 的其他常用属性List({space:8,initialIndex:0}){// space: 列表项间距initialIndex: 初始显示的索引ForEach(...);}.listDirection(Axis.Vertical)// 排列方向垂直/水平.divider({// 分割线也可以在 ListItem 里自己画strokeWidth:0.5,color:#E5E5E5,startMargin:16,endMargin:16}).lanes(2)// 多列类似 GridView.sticky(StickyStyle.Header)// 粘性头部sticky 粘性头部ListItemGroup({header:this.headerBuilder()}){ForEach(...);}.sticky(StickyStyle.Header)// 向上滚动时 header 固定在顶部适合分组列表如按城市分组的加油站。七、LazyForEach大数据量的性能优化ForEach会一次性渲染所有数据如果列表很长几百上千条会导致首屏慢。这时候用LazyForEach按需渲染只渲染可见区域的 ListItem// 1. 实现 IDataSource 接口classStationDataSourceimplementsIDataSource{privatelist:StationData[];constructor(list:StationData[]){this.listlist;}totalCount():number{returnthis.list.length;}getData(index:number):StationData{returnthis.list[index];}registerDataChangeListener(listener:DataChangeListener):void{}unregisterDataChangeListener(listener:DataChangeListener):void{}}// 2. 使用 LazyForEachletdataSourcenewStationDataSource(this.stationInfoList);List(){LazyForEach(dataSource,(item:StationData){ListItem(){this.stationInfoCard(item);}})}项目里只有 4 条数据用ForEach完全够用LazyForEach适合 50 条以上的数据。总结列表渲染的核心要点ListListItem列表容器和列表项ForEach的 key 函数用数据唯一 ID不用数组下标避免不必要的重建edgeEffect边缘效果推荐Fade空状态判断先判断数据再渲染加上空状态提示大数据量用LazyForEach代替ForEach按需渲染下一篇讲Builder 自定义构建函数——为什么要把 UI 片段抽到 Builder它和组件有什么区别。