Uniapp实现下拉刷新上拉加载更多功能开发实战教程
书接上篇,我们今天继续来学习一下 UniApp 如何实现页面顶部下拉刷新功能和页面底部上拉加载更多数据功能,附带了完整的设计思路和源码示例,以及常见避坑指南和开发实践。
此系列文章将带领你从移动端跨平台开发入门到精通,如果你也喜欢关注APP、小程序、公众号、H5等等应用的开发,可以持续关注后续更新,避免错过宝贵的知识分享。
致开发者的忠告: AI编程盛行的今天,我们并不是不需要学习技术,而是更应该专研技术,拥有把控全局的架构设计思维才能在AI盛行的未来有立足之地。
言归正传,咱们今天继续来聊一个几乎每个项目都跑不掉的需求(让页面交互活过来):下拉刷新页面数据,上拉加载更多内容。
你可能会想:“不就是加两个回调函数吗,有啥难的?”但很多新手写着写着就发现:下拉没反应、上拉不触发、scroll-view滚动不了、数据重复加载…… 然后开始怀疑人生。
别慌,今天咱就把这事儿彻底讲透。页面滚动和scroll-view各有什么优缺点?什么场景该用哪个?常见坑怎么绕过去?我统统掰开揉碎了喂给你。
一、先分清两个概念:页面滚动 vs 区域滚动
-
页面滚动:整个页面就是一个滚动容器,滚动条在
<page>上。→ 对应 UniApp 的 页面级生命周期:onPullDownRefresh、onReachBottom -
区域滚动:页面里某个固定高度的
<scroll-view>内部滚动。→ 对应 scroll-view 组件 的事件:@refresherrefresh(下拉)、@scrolltolower(上拉)
核心区别一句话:页面滚动是全局的,scroll-view 是局部的。用哪个,取决于你的页面布局。
二、下拉刷新:两种姿势,任你挑选
🅰️ 方案一:页面级下拉刷新(全页生效)
适合场景:整个页面就是一个列表,没有复杂的自定义头部,或者头部固定在顶部不参与滚动。
步骤 1:在 pages.json 里开启
{"pages": [ {"path": "pages/index/index","style": {"enablePullDownRefresh": true,"backgroundColor": "#f5f5f5"// 下拉背景色 } } ]}
步骤 2:在页面 JS 里监听 onPullDownRefresh
<scriptsetup>import { onPullDownRefresh } from'@dcloudio/uni-app'onPullDownRefresh(() => {console.log('下拉刷新触发')// 重新请求第一页数据fetchData(1).then(() => {// 数据刷新完毕,手动关闭下拉刷新动画uni.stopPullDownRefresh() })})</script>
✅ 优点:
-
零配置滚动容器,不用操心高度计算
-
下拉动画是系统级的,体验很顺滑
❌ 缺点:
-
整个页面都在下拉刷新范围内,如果你的页面顶部有导航栏、tab标签且不希望被下拉,就尴尬了
-
关闭刷新必须手动调
uni.stopPullDownRefresh(),容易忘
🅱️ 方案二:scroll-view 下拉刷新(区域生效)
适合场景:页面有固定头部(比如自定义导航栏、tab切换),只有列表区域可下拉。
代码示例
<template><view><!-- 固定的头部,不下拉 --><viewclass="header">我是头部</view><!-- 可滚动的列表区域 --><scroll-viewclass="scroll-list"scroll-yrefresher-enabled:refresher-triggered="refreshing"@refresherrefresh="onRefresh"><viewv-for="item in list":key="item.id">{{ item.title }}</view></scroll-view></view></template><scriptsetup>import { ref } from'vue'constrefreshing=ref(false)constlist=ref([])constonRefresh= () => {refreshing.value=truefetchData(1).then(() => {refreshing.value=false// 关闭下拉刷新状态 })}</script>
关键点:
-
refresher-enabled:开启下拉刷新 -
:refresher-triggered:控制刷新状态(true 显示加载动画,false 隐藏) -
@refresherrefresh:下拉刷新触发的事件
✅ 优点:
-
只对指定的滚动区域生效,不会拖拽整个页面
-
状态完全可控(
refresher-triggered)
❌ 缺点:
-
必须给 scroll-view 设置固定高度(或使用
flex:1+ 父容器固定高度),否则无法滚动 -
下拉动画没有页面级那么丝滑(但可接受)
三、上拉加载更多:同样两条路
🅰️ 页面级上拉加载(配合 onReachBottom)
适用场景:与页面级下拉刷新配套,整个页面滚动到底部触发。
步骤 1:pages.json 里可配置距离
{"path": "pages/index/index","style": {"onReachBottomDistance": 50// 默认也是50,单位px }}
步骤 2:页面 JS 监听
<scriptsetup>import { onReachBottom } from'@dcloudio/uni-app'onReachBottom(() => {console.log('触底了,加载下一页')loadMore()})</script>
✅ 优点:简单,无脑。❌ 缺点:
-
只要滚动到底部就触发,不管是不是真的有更多数据(需要你自己加锁)
-
如果页面底部有“已经到底了”提示,可能会在提示区域也被触发(需要自己控制距离)
🅱️ scroll-view 上拉加载(@scrolltolower)
适用场景:区域滚动时,滚动到底部触发。
<scroll-viewclass="scroll-list"scroll-y@scrolltolower="loadMore"lower-threshold="50"><viewv-for="item in list":key="item.id">{{ item.title }}</view><viewv-if="loading"class="loading">加载中...</view><viewv-if="finished"class="no-more">没有更多了</view></scroll-view>
lower-threshold:距离底部多少像素时触发(默认50)。
✅ 优点:
-
只针对这个滚动区域,不影响页面其他部分
-
可精细控制加载状态
❌ 缺点:
-
同下拉刷新,必须设置固定高度
-
如果列表数据不满一屏,无法滚动,
scrolltolower不会触发(这是符合预期的)
四、核心对比:页面滚动 vs scroll-view
| 对比维度 | 页面级滚动 (onPullDownRefresh / onReachBottom) |
scroll-view 滚动 |
|---|---|---|
| 适用布局 | 整页滚动,无固定头部/底部 | 固定头部/底部,列表区域独立滚动 |
| 高度设置 | 无需设置,自动占满 | 必须设置高度,否则不滚动 |
| 下拉刷新 | 需 pages.json 配置,自动动画,需手动停止 | 需 refresher-enabled,状态双向绑定 |
| 上拉加载 | onReachBottom 自动监听 |
@scrolltolower 监听 |
| 性能 | 较好(原生滚动) | 依赖渲染层,大数据量时稍逊 |
| 灵活性 | 低,全页面行为 | 高,可随意放置,可嵌套多个 |
| H5 兼容 | 完美 | 完美 |
| 小程序兼容 | 完美 | 完美 |
一句话选型口诀:
-
页面清爽无固定头 → 页面级滚动(简单省事)
-
头部固定 tab 切换 → scroll-view(区域滚动)
-
既要下拉又要上拉 → 建议统一用一种,别混用
五、不触发?不滚动?常见坑及填坑实录
💥 坑1:页面级下拉刷新死活不触发
现象:手指疯狂下拉,动画就是不出现。可能原因:
-
pages.json里没有设置"enablePullDownRefresh": true -
页面是
scroll-view滚动,页面本身没有滚动 → 页面级下拉刷新只在页面滚动时生效,如果页面被scroll-view撑满,页面本身不滚动,下拉刷新不会触发! -
小程序基础库版本过低(极少见)
✅ 解决方案:
-
确保
pages.json配置正确 -
如果你用了全屏的 scroll-view,并且希望有下拉刷新,直接在 scroll-view 上做,别用页面级的
💥 坑2:scroll-view 不滚动
现象:设置了 scroll-y,但内容超出后依然不能滚动。原因:没有给 scroll-view 固定高度。滚动的前提是:容器高度 < 内容高度。✅ 解决方案:
.scroll-list {height: 100vh; /* 固定视口高度,或 calc(100vh - 50px) *//* 或者使用 flex 布局父容器,给 scroll-view flex:1 */}
万能 flex 布局方案(推荐):
<template><viewclass="page"><viewclass="header">头部</view><scroll-viewclass="scroll-view"scroll-y> ...</scroll-view></view></template><style>.page {display: flex;flex-direction: column;height: 100vh; /* 父容器固定高度 */}.header {height: 44px;}.scroll-view {flex: 1; /* 剩余高度全部给 scroll-view */}</style>
💥 坑3:上拉加载触发了,但数据不满一屏,不应该触发啊?
页面级 onReachBottom:只要滚动到底部就触发,哪怕内容没占满一屏,只要用户试图上拉(触底)也会触发。解决方案:在加载更多逻辑里加判断:if (page >= totalPage) return;
scroll-view @scrolltolower:如果内容高度 <= scroll-view 高度,无法滚动,scrolltolower不会触发——这是正常的。不需要额外处理。
💥 坑4:数据重复加载
现象:下拉刷新和上拉加载同时触发,或者用户手速快连续触发了两次加载更多,导致数据重复。原因:没有加“锁”。✅ 解决方案:加一个 loading 状态,正在请求时不重复请求。
letisLoading=falseconstloadMore=async () => {if (isLoading) returnif (page>=totalPage) return// 没更多了isLoading=truetry {constres=awaitfetchData(page+1)list.value.push(...res.data)page.value++ } finally {isLoading=false }}
💥 坑5:scroll-view 下拉刷新与页面下拉冲突
场景:页面既有页面级下拉刷新(通过 pages.json 配置),内部又有一个 scroll-view 下拉刷新。结果:两者都会触发,或者互相抢夺手势。✅ 解决方案:二选一,不要混用。通常如果用了 scroll-view 下拉,就在 pages.json 里关掉 enablePullDownRefresh。
💥 坑6:H5 端页面级下拉刷新不流畅
现象:H5 端下拉刷新动画有点卡,或者下拉时整个页面被拖拽。原因:浏览器自带的下拉回弹效果与 UniApp 的下拉刷新叠加。✅ 解决方案:H5 端建议使用 scroll-view + 自定义下拉刷新,或者使用 @dcloudio/uni-pull2refresh 插件。
六、一个真实案例:商品列表页,顶部有分类 tab
很多电商小程序都是这种结构:
-
顶部:轮播图/搜索框
-
中间:分类 tab
-
底部:商品列表(可滚动、下拉刷新、上拉加载)
错误做法:用页面级滚动,把整个页面作为滚动容器。结果用户下拉时,把轮播图和 tab 都拉下来了,体验很怪。
正确做法:
-
轮播图 + tab 固定在上方,不参与滚动
-
商品列表区域用
scroll-view独立滚动,内部实现下拉刷新、上拉加载
代码骨架:
<template><viewclass="page"><!-- 固定头部 --><viewclass="fixed-header"><swiper>...</swiper><viewclass="tabs">...</view></view><!-- 可滚动列表 --><scroll-viewclass="goods-list"scroll-yrefresher-enabled:refresher-triggered="refreshing"@refresherrefresh="handleRefresh"@scrolltolower="loadMore"><goods-itemv-for="item in list"/><uni-load-more:status="loadMoreStatus"/></scroll-view></view></template>
完美解决。
七、总结:选型决策树
问自己三个问题:
-
页面是否有固定头部/底部,且这些区域不参与滚动?→ 是:用 scroll-view(区域滚动)→ 否:走下一步
-
是否在意页面级下拉刷新把整个页面都拉下来的效果?→ 在意:用 scroll-view→ 不在意:可考虑页面级(简单)
-
列表数据量极大(几千条)?→ 是:推荐页面级滚动,性能更好(原生滚动)→ 否:两种皆可,按布局选
八、最后的碎碎念
小老弟,下拉刷新和上拉加载就像吃饭喝水一样,是每个前端的基本功。不要畏惧 scroll-view 的高度计算,那是你从“只会写页面”到“会做布局”的必经之路。
花半小时把今天讲的几个坑手动复现一遍,再亲手修好它们。以后你面试被问到“怎么实现下拉加载”,不仅能回答上来,还能说出“页面级适合什么、scroll-view 适合什么、坑在哪”,这就碾压一票只会复制粘贴的人。
今天就到这儿,去练练手吧!遇到奇怪的问题,欢迎随时带着代码来敲我。
—— 一个曾经也被滚动折磨过的老前辈 🛋️
加油,未来的全栈大佬!💪如果你也对移动端跨端开发感兴趣,关注我,后续还有更多优质文章分享!


往期相关文章推荐
夜雨聆风
