在移动端应用开发中,列表页面的下拉刷新与上拉加载更多几乎是最常见的交互需求之一。无论是资讯流、商品列表还是社交动态,用户都期望通过简单的手势操作获取最新内容或加载更多数据。UniApp 作为跨平台开发框架,提供了统一的 API 支持这两种交互,但在实际项目中,如何设计一套通用、可复用、易维护的方案,仍然值得深入探讨。本文将围绕 UniApp 的下拉刷新与上拉加载更多,从基础配置到通用封装,逐步展开。
一、基础配置:页面级与组件级
UniApp 中实现下拉刷新有两种方式:页面级和组件级(scroll-view)。页面级通过 pages.json 配置 enablePullDownRefresh: true,并在页面中监听 onPullDownRefresh 和 onReachBottom 生命周期。这种方式适合整页列表,实现简单,但灵活性有限,比如无法自定义下拉动画样式。
组件级则使用 <scroll-view> 的 refresher-enabled 和 @scrolltolower 事件,适合局部滚动区域或需要自定义下拉样式的场景。选择哪种方式取决于产品需求:如果页面本身就是列表页,页面级更省事;如果列表嵌在复杂布局中,组件级更合适。
二、页面级方案的核心逻辑
页面级方案的关键在于两个生命周期函数:
onPullDownRefresh:用户下拉时触发,通常用于重置页码、清空列表、重新请求第一页数据。onReachBottom:页面滚动到底部时触发,用于加载下一页数据。
一个典型的实现需要维护以下状态:
data() {
return {
list: [], // 列表数据
page: 1, // 当前页码
pageSize: 10, // 每页条数
total: 0, // 总条数
loading: false, // 是否正在加载
finished: false // 是否已加载完所有数据
}
}
下拉刷新时,将 page 重置为 1,清空 list,然后调用请求方法。上拉加载时,先判断 loading 和 finished,避免重复请求和无效请求,再将 page 加 1 后请求数据。请求成功后,将新数据追加到 list,并更新 total 和 finished 状态。
需要注意的是,onPullDownRefresh 执行完毕后必须调用 uni.stopPullDownRefresh(),否则下拉动画不会消失。而 onReachBottom 的触发距离可以在 pages.json 中通过 onReachBottomDistance 配置,默认 50px。
三、封装通用混入(mixin)
如果每个列表页面都重复写上述逻辑,代码会变得冗余且难以维护。UniApp 支持 Vue 的 mixin,我们可以将下拉刷新与上拉加载的通用逻辑抽离成一个 mixin,页面只需关注数据请求本身。
// mixins/listMixin.js
export default {
data() {
return {
list: [],
page: 1,
pageSize: 10,
total: 0,
loading: false,
finished: false
}
},
methods: {
async loadData(reset = false) {
if (this.loading || (this.finished && !reset)) return
this.loading = true
if (reset) {
this.page = 1
this.finished = false
}
try {
const res = await this.fetchData(this.page, this.pageSize)
const rows = res.data.rows || []
this.total = res.data.total || 0
this.list = reset ? rows : [...this.list, ...rows]
this.finished = this.list.length >= this.total
this.page++
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
uni.stopPullDownRefresh()
}
},
// 子页面必须实现 fetchData
fetchData(page, pageSize) {
return Promise.reject(new Error('请实现 fetchData 方法'))
}
},
onPullDownRefresh() {
this.loadData(true)
},
onReachBottom() {
this.loadData()
}
}
页面中使用时,只需引入 mixin 并实现 fetchData 方法:
import listMixin from '@/mixins/listMixin.js'
export default {
mixins: [listMixin],
methods: {
async fetchData(page, pageSize) {
return await uni.request({
url: 'https://api.example.com/list',
data: { page, pageSize }
})
}
}
}
这样,任何列表页面只需几行代码即可获得完整的分页加载能力,大大提升了开发效率。
四、组件级方案的通用封装
对于使用 <scroll-view> 的场景,可以封装一个通用的列表组件,对外暴露 load 事件和 finished、loading 等属性。组件内部处理滚动到底部、下拉刷新等逻辑,父组件只需传入数据和监听事件。
<template>
<scroll-view
scroll-y
:refresher-enabled="true"
:refresher-triggered="triggered"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<slot />
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="finished" class="finished">没有更多了</view>
</scroll-view>
</template>
<script>
export default {
props: {
loading: Boolean,
finished: Boolean
},
data() {
return { triggered: false }
},
methods: {
onRefresh() {
this.triggered = true
this.$emit('refresh', () => {
this.triggered = false
})
},
onLoadMore() {
if (!this.loading && !this.finished) {
this.$emit('loadmore')
}
}
}
}
</script>
父组件通过 @refresh 和 @loadmore 事件处理数据请求,并在请求完成后调用回调关闭下拉状态。这种封装方式适合需要自定义下拉动画或列表嵌在弹窗、选项卡中的场景。
五、常见问题与优化建议
在实际开发中,有几个细节容易踩坑。第一,重复请求问题:上拉加载时如果用户快速滑动,onReachBottom 可能连续触发多次,必须在方法入口处用 loading 标志拦截。第二,数据为空时的处理:如果第一页返回空数组,应直接设置 finished = true,避免用户继续上拉。第三,下拉刷新与上拉加载的并发:下拉刷新时应取消或忽略正在进行的上拉请求,可以通过请求序号或 AbortController 实现。
此外,还可以考虑以下优化:使用骨架屏提升首屏体验;对图片列表使用懒加载;在 finished 为 true 时显示“没有更多了”的提示;对网络异常情况提供重试按钮。这些细节虽小,却能显著提升用户体验。
六、总结
UniApp 的下拉刷新与上拉加载更多,本质上是对分页请求的状态管理。页面级方案简单直接,适合大多数场景;组件级方案灵活可控,适合复杂布局。通过 mixin 或通用组件封装,可以将重复逻辑收敛到一处,让业务页面保持简洁。无论选择哪种方式,核心都在于维护好 page、loading、finished 这三个状态,并处理好边界情况。希望本文的方案能为你的 UniApp 项目提供参考,让列表交互不再成为重复劳动的负担。
未经允许不得转载:任鹏个人博客 » UniApp 中实现下拉刷新与上拉加载更多的通用方案


朋友圈点赞图在线生成源码