乐于分享
好东西不私藏

UniApp + Vue3 实战:打造高性能下拉刷新 & 上拉加载列表

UniApp + Vue3 实战:打造高性能下拉刷新 & 上拉加载列表

在移动端开发中,下拉刷新、上拉加载更多是列表类页面的标配功能。本文基于 UniApp + Vue3(组合式 API)技术栈,从零实现一个高性能、可复用的列表加载组件,兼顾代码优雅性和用户体验。

一、需求分析

你需要实现的核心功能包括:
页面初始加载数据,显示加载中状态
下拉页面触发刷新,重置列表数据并重新请求第一页
上拉到底部触发加载更多,加载下一页数据
处理无数据、加载失败、无更多数据等边界状态
避免重复请求(如加载中时禁止再次触发)

二、技术准备

基础环境:已搭建 UniApp + Vue3 项目(CLI 或 HBuilderX 创建均可)
核心 API:

三、代码实现

1. 页面结构(template)

<template>  <viewclass="list-page">    <!-- 列表容器 -->    <scroll-view      class="list-container"       scroll-y       refresher-enabled       :refresher-triggered="refreshing"      @refresherrefresh="handleRefresh"      @scrolltolower="handleLoadMore"    >      <!-- 列表内容 -->      <viewclass="list-item"v-for="(item, index) in listData":key="index">        <viewclass="item-title">{{ item.title }}</view>        <viewclass="item-desc">{{ item.desc }}</view>      </view>      <!-- 状态提示 -->      <uni-load-more        :status="loadStatus"         :content-text="loadText"        class="load-more"      />    </scroll-view>    <!-- 初始加载/加载失败占位 -->    <viewclass="empty-state"v-if="!listData.length && !loading">      <textclass="empty-text">{{ emptyText }}</text>      <buttonclass="reload-btn" @click="fetchList(1)"v-if="loadFailed">重新加载</button>    </view>    <!-- 初始加载中 -->    <viewclass="loading-state"v-if="loading && !listData.length">      <uni-load-morestatus="loading" />    </view>  </view></template>

2. 逻辑实现(script setup)

<scriptsetup>import { ref, reactive, onMounted } from 'vue'// 列表核心状态const listData = ref([]) // 列表数据const loading = ref(false// 加载中(初始/加载更多)const refreshing = ref(false// 下拉刷新中const loadFailed = ref(false// 加载失败const loadStatus = ref('more'// 加载更多状态:more-可加载,noMore-无更多,error-失败const pageInfo = reactive({ // 分页信息  page1,  size10,  total0})// 加载文案自定义const loadText = ref({  contentdown'上拉加载更多',  contentrefresh'正在加载...',  contentnomore'已加载全部数据'})const emptyText = ref('加载中...')/** * 模拟接口请求(实际项目替换为真实接口) * @param {Number} page 页码 * @returns {Promise} 数据Promise */const fetchApi = (page) => {  return new Promise((resolve, reject) => {    // 模拟网络延迟    setTimeout(() => {      // 模拟随机失败(便于测试失败场景)      if (Math.random() < 0.1 && page !== 1) {        reject(new Error('接口请求失败'))        return      }      // 模拟数据      const mockData = Array.from({ length: pageInfo.size }, (_, i) => ({        id: (page - 1) * pageInfo.size + i + 1,        title`列表项 ${(page - 1) * pageInfo.size + i + 1}`,        desc`这是第 ${page} 页的第 ${i + 1} 条数据`      }))      // 模拟总条数(共3页数据)      const total = 28      resolve({ list: mockData, total })    }, 800)  })}/** * 获取列表数据 * @param {Number} page 页码(1表示刷新,其他表示加载更多) */const fetchList = async (page) => {  // 避免重复请求  if (loading.valuereturn  loading.value = true  loadFailed.value = false  emptyText.value = '加载中...'  try {    const res = await fetchApi(page)    // 刷新:重置列表;加载更多:追加列表    if (page === 1) {      listData.value = res.list    } else {      listData.value = [...listData.value, ...res.list]    }    // 更新分页信息    pageInfo.page = page    pageInfo.total = res.total    // 判断是否有更多数据    if (listData.value.length >= pageInfo.total) {      loadStatus.value = 'noMore'    } else {      loadStatus.value = 'more'    }    // 空数据提示    if (!listData.value.length) {      emptyText.value = '暂无数据'    }  } catch (err) {    console.error('列表加载失败:', err)    loadFailed.value = true    loadStatus.value = 'error'    emptyText.value = '加载失败,请重试'  } finally {    loading.value = false    refreshing.value = false // 结束下拉刷新状态    // 停止UniApp原生下拉刷新(兼容非scroll-view的下拉刷新)    uni.stopPullDownRefresh()  }}/** * 下拉刷新处理 */const handleRefresh = () => {  if (refreshing.valuereturn  refreshing.value = true  pageInfo.page = 1 // 重置页码  fetchList(1)}/** * 上拉加载更多处理 */const handleLoadMore = () => {  // 过滤无效触发:加载中、无更多、加载失败  if (loading.value || loadStatus.value === 'noMore' || loadStatus.value === 'error') {    return  }  fetchList(pageInfo.page + 1)}// 页面初始化加载onMounted(() => {  fetchList(1)})</script>

3. 样式美化(style)

<stylescoped>.list-page {  width100%;  height100vh;  background-color#f5f5f5;}.list-container {  width100%;  height100%;}.list-item {  padding20rpx;  margin10rpx;  background-color#fff;  border-radius10rpx;}.item-title {  font-size32rpx;  font-weight600;  margin-bottom10rpx;}.item-desc {  font-size28rpx;  color#666;}.load-more {  padding20rpx 0;  text-align: center;}.empty-state.loading-state {  display: flex;  flex-direction: column;  align-items: center;  justify-content: center;  height50vh;}.empty-text {  font-size28rpx;  color#999;  margin-bottom20rpx;}.reload-btn {  padding10rpx 30rpx;  background-color#007aff;  color#fff;  border-radius20rpx;  font-size28rpx;}</style>

四、关键点解析

1. 状态管理

使用ref管理简单类型状态(如loading、refreshing),reactive管理复杂对象(如pageInfo),符合 Vue3 组合式 API 最佳实践。

拆分不同状态(加载中、刷新中、加载失败、无更多),避免状态混乱,提升代码可维护性。

2. 防重复请求

在fetchList开头判断loading状态,避免同一时间多次触发请求。

上拉加载更多时,过滤加载中、无更多、加载失败等无效状态,防止无效请求。

3. 兼容与体验优化

使用scroll-view的下拉刷新(refresher-enabled)而非 UniApp 全局下拉刷新,更灵活可控。

最终通过uni.stopPullDownRefresh()兼容全局下拉刷新,确保状态统一。

区分 “初始加载” 和 “加载更多” 的状态提示,提升用户体验。

4. 接口适配

封装fetchApi函数,实际项目中只需替换为真实接口请求(如使用uni.request或封装的 axios),核心逻辑无需改动。

五、功能扩展建议

节流处理:对上拉触底事件添加节流(如 500ms 内只触发一次),避免快速滑动时频繁触发。
图片懒加载:列表中如有图片,使用 UniApp 内置的 lazy-load 属性优化性能。

骨架屏:替换简单的加载提示,使用骨架屏提升视觉体验。

组件封装:将列表逻辑抽离为独立组件(如ListLoad.vue),通过 props 传递接口地址、分页参数等,实现跨页面复用。
数据缓存:结合uni.setStorageSync缓存列表数据,页面返回时无需重新请求。

总结

UniApp + Vue3 实现下拉刷新 & 上拉加载的核心是结合scroll-view事件(refresherrefresh/scrolltolower)和分页逻辑。
状态管理需拆分清晰(加载中、刷新中、失败、无更多),避免状态冲突导致的体验问题。
防重复请求、兼容处理、用户体验优化是此类功能的关键,也是生产环境必备的考量。
本文实现的列表组件兼顾了功能性和可维护性,你可以直接将代码复制到 UniApp 项目中运行,也可根据实际业务需求调整扩展,适配不同的列表场景。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » UniApp + Vue3 实战:打造高性能下拉刷新 & 上拉加载列表

评论 抢沙发

6 + 9 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮