UniApp 中实现下拉刷新与上拉加载更多的通用方案

在移动端应用开发中,列表页面的下拉刷新与上拉加载更多几乎是最常见的交互需求之一。无论是资讯流、商品列表还是社交动态,用户都期望通过简单的手势操作获取最新内容或加载更多数据。UniApp 作为跨平台开发框架,提供了统一的 API 支持这两种交互,但在实际项目中,如何设计一套通用、可复用、易维护的方案,仍然值得深入探讨。本文将围绕 UniApp 的下拉刷新与上拉加载更多,从基础配置到通用封装,逐步展开。

一、基础配置:页面级与组件级

UniApp 中实现下拉刷新有两种方式:页面级和组件级(scroll-view)。页面级通过 pages.json 配置 enablePullDownRefresh: true,并在页面中监听 onPullDownRefreshonReachBottom 生命周期。这种方式适合整页列表,实现简单,但灵活性有限,比如无法自定义下拉动画样式。

组件级则使用 <scroll-view>refresher-enabled@scrolltolower 事件,适合局部滚动区域或需要自定义下拉样式的场景。选择哪种方式取决于产品需求:如果页面本身就是列表页,页面级更省事;如果列表嵌在复杂布局中,组件级更合适。

二、页面级方案的核心逻辑

页面级方案的关键在于两个生命周期函数:

  • onPullDownRefresh:用户下拉时触发,通常用于重置页码、清空列表、重新请求第一页数据。
  • onReachBottom:页面滚动到底部时触发,用于加载下一页数据。

一个典型的实现需要维护以下状态:

data() {
  return {
    list: [],        // 列表数据
    page: 1,         // 当前页码
    pageSize: 10,    // 每页条数
    total: 0,        // 总条数
    loading: false,  // 是否正在加载
    finished: false  // 是否已加载完所有数据
  }
}

下拉刷新时,将 page 重置为 1,清空 list,然后调用请求方法。上拉加载时,先判断 loadingfinished,避免重复请求和无效请求,再将 page 加 1 后请求数据。请求成功后,将新数据追加到 list,并更新 totalfinished 状态。

需要注意的是,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 事件和 finishedloading 等属性。组件内部处理滚动到底部、下拉刷新等逻辑,父组件只需传入数据和监听事件。

<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 或通用组件封装,可以将重复逻辑收敛到一处,让业务页面保持简洁。无论选择哪种方式,核心都在于维护好 pageloadingfinished 这三个状态,并处理好边界情况。希望本文的方案能为你的 UniApp 项目提供参考,让列表交互不再成为重复劳动的负担。

未经允许不得转载:任鹏个人博客 » UniApp 中实现下拉刷新与上拉加载更多的通用方案

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏