onreachbottom 默认监听页面滚动,套在scroll-view内则失效;需改用@scrolltolower或确保页面可滚动。分页需加loading锁和nomore标记,避免重复请求;下拉刷新时同步重置相关状态;列表渲染推荐concat+唯一key优化。

uni-app 中 onReachBottom 触发时机与常见失效原因
直接写 onReachBottom 却没反应?大概率不是逻辑错,而是页面滚动容器没被正确识别。uni-app 的 onReachBottom 默认监听的是整个页面(page)的滚动,但如果你把列表套在 <scroll-view></scroll-view> 里,它就完全不触发——因为此时滚动发生在组件内,页面本身没滚动。
解决方式分两种:
- 不用
<scroll-view></scroll-view>,让列表撑满页面高度,依赖原生页面滚动(推荐,性能好、兼容稳) - 必须用
<scroll-view></scroll-view>,就改用它的@scrolltolower事件,并确保设置scroll-y="true"和显式高度(如style="height: 600rpx;") - 检查
pages.json里对应页面的"disableScroll": true是否误开启——开了就禁用了页面滚动,onReachBottom必然失效
分页请求时如何避免重复触发和状态混乱
用户快速上滑,onReachBottom 可能连续触发多次,导致同一页数据被反复请求,甚至出现“加载中”叠加、“页码错乱”等问题。
核心是加一层请求锁 + 页码管理:
- 用一个响应式变量如
loading控制按钮/提示显示,请求开始设为true,结束(无论成功失败)必须设回false - 页码变量(如
pageNo)只在请求成功且有数据时才自增:if (res.data.length > 0) this.pageNo++;如果后端返回空数组,说明到底了,就该停掉后续请求 - 请求前加守卫:
if (this.loading || this.noMore) return,其中noMore是后端明确返回“无更多数据”后置为true的标记
下拉刷新与上拉加载共存时的冲突处理
uni-app 的 onPullDownRefresh 和 onReachBottom 理论上可共存,但实际容易出问题:比如下拉刷新中重置了 pageNo = 1,但用户还没松手,onReachBottom 就已触发,结果刚刷完第 1 页,立刻又去拉第 2 页。
稳妥做法是解耦两者的状态控制:
- 下拉刷新时,除了重置数据和页码,**同步置
noMore = false和loading = false**,否则后续上拉可能被拦截 - 在
onPullDownRefresh结束回调里调用uni.stopPullDownRefresh(),但不要在这里发起首次请求——等onLoad或手动触发更可控 - 如果使用
<pull-down-refresh></pull-down-refresh>组件(H5/小程序平台),注意它和onPullDownRefresh是互斥的,只能选一种方案
真实场景下的数据拼接与渲染优化
单纯 this.list.push(...res.data) 在长列表中会引发卡顿,尤其 iOS 下 Vue 响应式追踪大量节点开销明显。
两个轻量但有效的优化点:
- 用
concat替代push:生成新数组比修改原数组更利于 Vue diff,也避免意外污染原始数据 ——this.list = this.list.concat(res.data) - 对列表项加
key,且必须是唯一稳定值(如后端返回的id),别用:key="index",否则翻页后顺序变化会导致组件复用错乱 - 如果单页数据量大(>50 条),考虑配合
<recycle-list></recycle-list>(uni-app 内置)或虚拟滚动方案,但注意它目前仅支持固定高度 item
后端返回字段是否带分页元信息(如 total、has_next)很关键。比起前端靠 res.data.length 判断是否到底,直接读 <code>res.has_next === false 更可靠——网络抖动或数据删减都可能导致长度判断失准。











