在uni-app x中,<list-view>和<waterfall-view>是专门为解决大数据量、长列表渲染卡顿/内存暴涨而设计的两个纯原生核心组件。
与传统的 Web App(DOM 节点无限增加导致页面卡死)不同,这两个组件在 App 端直接映射为 Android 的RecyclerView和 iOS 的UICollectionView,具备真正的原生 View 节点回收复用池。
一、 传统<scroll-view>vs 原生<list-view>区别
| 对比项 | <scroll-view>(传统) | <list-view>/<waterfall-view> |
|---|---|---|
| 底层实现 | 纯滚动容器,把所有节点都加载到内存中 | 原生列表控件,屏幕外节点自动销毁并回收复用 |
| 内存开销 | 随数据增加呈线性增长(数千条数据必然卡死/爆内存) | 内存始终保持在一个常数级(仅保留屏幕可见项 + 缓冲项) |
| 适用场景 | 几条到几十条数据的短页面、局部滚动区域 | 几百到数万条数据的长列表、下拉刷新/上拉加载列表、商城瀑布流 |
二、<list-view>组件详解
<list-view>用于实现垂直或水平方向的单列/多列普通长列表(如:聊天消息列表、医生列表、问诊历史、处方记录等)。
1. 核心配套组件:<list-item>与type机制
list-view的直接子节点必须是<list-item>。 其中最重要的属性是type(复用类型标识):
- 复用原理:当列表向上滚动时,滑出屏幕顶部的
<list-item>节点不会被销毁,而是根据它的type值被放入原生复用池。当底部有新数据要滑入时,系统直接从复用池拿出该节点,仅更新里面的文本和图片数据。 - 规则:
- 如果列表项的UI 样式完全一致,给它们设置相同的
type(如type="0")。 - 如果列表项有多种不同结构(例如聊天页面:左侧医生消息、右侧患者消息、中间系统提示),必须给不同结构的子项设置不同的
type(如type="1"、type="2")。
- 如果列表项的UI 样式完全一致,给它们设置相同的
2. 吸顶效果支持 (<sticky-header>)
无需任何 JS 计算滚动高度,在<list-view>中搭配<sticky-header>或<sticky-section>组件,即可实现纯原生的流畅分组标题吸顶效果。
3. 常见属性与事件
scroll-y/scroll-x:开启垂直或水平滚动。refresher-enabled:开启原生下拉刷新。@refresherrefresh:下拉刷新触发事件。@scrolltolower:滚动到底部触发(上拉加载更多)。show-scrollbar:是否显示原生滚动条。bounces:是否开启回弹效果(iOS 原生弹性回弹)。
三、<waterfall-view>瀑布流组件详解
<waterfall-view>专门用于参差不齐高度的卡片瀑布流布局(如:中医健康科普文章瀑布流、中药材/养生产品商城瀑布流)。
1. 解决的痛点
在 Web 时代做瀑布流,通常需要 JS 实时计算左右两列的高度,或者用absolute绝对定位,这会导致频繁触发 Web 重排(Reflow),极为耗性能。<waterfall-view>由 Native 原生 C++ 引擎(AndroidStaggeredGridLayoutManager/ iOSUICollectionViewCompositionalLayout)自动在底层计算卡片高矮,零 JS 布局开销。
2. 专属关键属性
column-count:瀑布流列数(默认2列)。column-gap:列与列之间的横向间距。row-gap:卡片与卡片之间的纵向间距。custom-header/custom-footer:支持在瀑布流顶部或底部插入跨全宽的 Banner 或加载状态。
四、.uvue实战代码示例
1. 标准<list-view>列表代码示例
<template> <view class="container"> <list-view class="list-container" :scroll-y="true" :refresher-enabled="true" :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore" > <!-- 普通消息项 (type="0") --> <list-item v-for="(item, index) in dataList" :key="item.id" :type="0" class="list-card" > <image class="avatar" :src="item.avatar"></image> <view class="info"> <text class="title">{{ item.name }}</text> <text class="desc">{{ item.diagResult }}</text> </view> </list-item> <!-- 底部加载更多提示 --> <list-item :type="99" class="loading-item"> <text class="loading-text">{{ isLoading ? '加载中...' : '没有更多了' }}</text> </list-item> </list-view> </view> </template> <script setup uts> type DoctorItem = { id: string name: string avatar: string diagResult: string } const dataList = ref<Array<DoctorItem>>([]) const isRefreshing = ref(false) const isLoading = ref(false) const onRefresh = () => { isRefreshing.value = true // 模拟刷新数据 setTimeout(() => { isRefreshing.value = false }, 1000) } const onLoadMore = () => { if (isLoading.value) return isLoading.value = true // 模拟加载下一页 } </script> <style scoped> .container { flex: 1; } .list-container { flex: 1; } .list-card { flex-direction: row; padding: 15px; border-bottom-width: 1px; border-bottom-color: #eee; background-color: #ffffff; } .avatar { width: 50px; height: 50px; border-radius: 25px; } .info { margin-left: 12px; justify-content: center; } .title { font-size: 16px; font-weight: bold; } .desc { font-size: 14px; color: #666; margin-top: 4px; } </style>2. 瀑布流<waterfall-view>代码示例
<template> <waterfall-view class="waterfall-container" :column-count="2" :column-gap="10" :row-gap="10" @scrolltolower="onLoadMore" > <!-- 瀑布流卡片 (type="1") --> <list-item v-for="article in articleList" :key="article.id" :type="1" class="article-card" > <image class="cover" :src="article.cover" mode="widthFix"></image> <text class="article-title">{{ article.title }}</text> <text class="author">作者:{{ article.author }}</text> </list-item> </waterfall-view> </template>五、 性能调优三大黄金法则
- 必须使用
<list-item>包裹并指定type: 千万不要在<list-view>里直接放普通的<view>,否则原生节点无法进行回收复用,就失去了使用list-view的意义。 - 不同布局结构的子项必须区分
type: 如果子项 A 是带图卡片(type="1"),子项 B 是纯文本标题(type="2"),绝不能将它们的type设为相同,否则复用时会导致 UI 错乱。 - 保持子项层级平坦:
<list-item>内部的节点嵌套层级越浅,原生 View 布局计算速度就越快,滚动流畅度越高。