uni-app 触底加载和下拉刷新完整实现指南

一、功能概述

1.1 下拉刷新(Pull Down Refresh)

下拉刷新是指用户通过下拉手势触发页面数据重新加载的功能。当用户下拉页面时,会显示一个刷新指示器,数据加载完成后自动隐藏。

1.2 触底加载(Reach Bottom Load More)

触底加载是指当用户滚动到页面底部时,自动加载下一页数据并追加到现有列表中,实现无限滚动的效果。

二、实现步骤

2.1 页面配置

首先,需要在 pages.json 中配置页面,启用下拉刷新功能:

json 复制代码
{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "navigationStyle": "custom",
        "enablePullDownRefresh": true  // 启用下拉刷新
      }
    }
  ]
}

2.2 数据结构设计

在实现分页加载之前,我们需要定义相关的状态变量:

javascript 复制代码
// 新闻列表
const newsList = ref([]);

// 分页相关
const pageNum = ref(1);        // 当前页码,从第1页开始
const pageSize = ref(10);      // 每页数据量,默认10条
const hasMore = ref(true);     // 是否还有更多数据
const loading = ref(false);    // 是否正在加载

变量说明:

  • pageNum:当前请求的页码,用于分页请求
  • pageSize:每页返回的数据条数
  • hasMore:标识是否还有更多数据可以加载
  • loading:防止重复请求的加载状态

2.3 核心函数实现

2.3.1 获取列表数据函数

这是整个功能的核心函数,需要支持首次加载和加载更多两种模式:

javascript 复制代码
// 获取列表
const getHeadNewsList = (filterCode = null, isLoadMore = false) => {
  // 如果正在加载,直接返回已解决的Promise
  if (loading.value) {
    return Promise.resolve();
  }

  // 如果是加载更多但没有更多数据,直接返回
  if (isLoadMore && !hasMore.value) {
    return Promise.resolve();
  }

  // 如果不是加载更多,重置分页
  if (!isLoadMore) {
    pageNum.value = 1;
    hasMore.value = true;
  }

  loading.value = true;

  const params = {
    pageNum: pageNum.value,
    pageSize: pageSize.value,
  };

  if (filterCode !== null) {
    params.newsType = filterCode;
  }

  return homeApi()
    .getHeadNewsList(params)
    .then((res) => {
      console.log(res, "列表");
      loading.value = false;

      // 接口返回结构:{total: 9999, size: 10, items: [...]}
      if (res && res.items && Array.isArray(res.items)) {
        if (isLoadMore) {
          // 加载更多,追加数据
          newsList.value = [...newsList.value, ...res.items];
        } else {
          // 首次加载或切换筛选,替换数据
          newsList.value = res.items;
        }

        // 判断是否还有更多数据
        const total = res.total || 0;
        const currentTotal = newsList.value.length;
        hasMore.value = currentTotal < total;

        // 如果还有更多数据,页码+1,为下次加载做准备
        if (hasMore.value && res.items.length > 0) {
          pageNum.value += 1;
        }
      } else {
        hasMore.value = false;
      }
    })
    .catch((error) => {
      console.error("获取列表失败:", error);
      loading.value = false;
      throw error;
    });
};

关键点解析:

  1. 防重复请求 :通过 loading.value 判断,如果正在加载则直接返回,避免重复请求。

  2. 数据追加 vs 替换

    • isLoadMore = true:追加数据,使用扩展运算符合并数组
    • isLoadMore = false:替换数据,直接赋值新数组
  3. 分页逻辑

    • 首次加载或刷新时,重置 pageNum = 1
    • 加载成功后,如果还有更多数据,页码自动 +1,为下次加载做准备
  4. 判断是否还有更多数据

    javascript 复制代码
    const total = res.total || 0;  // 总数据量
    const currentTotal = newsList.value.length;  // 当前已加载的数据量
    hasMore.value = currentTotal < total;  // 如果已加载数量小于总数,说明还有更多
2.3.2 下拉刷新实现

使用 uni-app 的 onPullDownRefresh 生命周期函数:

javascript 复制代码
import { onPullDownRefresh } from "@dcloudio/uni-app";

// 下拉刷新
onPullDownRefresh(() => {
  console.log("下拉刷新");
  // 重置分页
  pageNum.value = 1;
  hasMore.value = true;

  // 重新获取数据
  Promise.all([
    getBannerList(),           // 获取轮播图
    getHeadNewsFilter(),       // 获取筛选标签
    getHeadNewsList(activeFilter.value, false)  // 获取新闻列表
  ]).finally(() => {
    // 停止下拉刷新动画
    uni.stopPullDownRefresh();
  });
});

实现要点:

  1. 重置状态:刷新时需要重置分页状态,确保从第一页开始加载。

  2. 并行请求 :使用 Promise.all 同时请求多个接口,提高加载效率。

  3. 停止动画 :无论请求成功还是失败,都要在 finally 中调用 uni.stopPullDownRefresh() 停止刷新动画。

2.3.3 触底加载实现

使用 uni-app 的 onReachBottom 生命周期函数:

javascript 复制代码
import { onReachBottom } from "@dcloudio/uni-app";

// 触底加载
onReachBottom(() => {
  console.log("触底加载");
  if (hasMore.value && !loading.value) {
    getHeadNewsList(activeFilter.value, true);
  }
});

实现要点:

  1. 条件判断:只有在有更多数据且不在加载中时才触发加载。

  2. 加载更多模式 :传入 isLoadMore = true,确保数据是追加而不是替换。

2.4 UI 状态展示

在模板中添加加载状态和没有更多数据的提示:

vue 复制代码
<template>
  <!-- 列表 -->
  <view class="news-list">
    <view
      class="news-item"
      v-for="(item, index) in newsList"
      :key="item.id || index"
      @click="handleNewsClick(item)"
    >
      <!-- 内容 -->
    </view>

    <!-- 加载状态 -->
    <view class="loading-more" v-if="loading">
      <text class="loading-text">加载中...</text>
    </view>

    <!-- 没有更多数据 -->
    <view class="no-more" v-if="!hasMore && newsList.length > 0">
      <text class="no-more-text">没有更多数据了</text>
    </view>
  </view>
</template>

样式定义:

css 复制代码
.loading-more {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40rpx 0;
}

.loading-text {
  font-size: 24rpx;
  color: #999999;
}

.no-more {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40rpx 0;
}

.no-more-text {
  font-size: 24rpx;
  color: #999999;
}

三、完整代码示例

3.1 Script 部分

javascript 复制代码
<script setup>
import { ref } from "vue";
import { onLoad, onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app";
import { homeApi } from "@/api/home/index";

// 数据定义
const newsList = ref([]);
const pageNum = ref(1);
const pageSize = ref(10);
const hasMore = ref(true);
const loading = ref(false);
const activeFilter = ref(null);

// 获取列表数据
const getHeadNewsList = (filterCode = null, isLoadMore = false) => {
  if (loading.value) {
    return Promise.resolve();
  }

  if (isLoadMore && !hasMore.value) {
    return Promise.resolve();
  }

  if (!isLoadMore) {
    pageNum.value = 1;
    hasMore.value = true;
  }

  loading.value = true;

  const params = {
    pageNum: pageNum.value,
    pageSize: pageSize.value,
  };

  if (filterCode !== null) {
    params.newsType = filterCode;
  }

  return homeApi()
    .getHeadNewsList(params)
    .then((res) => {
      loading.value = false;

      if (res && res.items && Array.isArray(res.items)) {
        if (isLoadMore) {
          newsList.value = [...newsList.value, ...res.items];
        } else {
          newsList.value = res.items;
        }

        const total = res.total || 0;
        const currentTotal = newsList.value.length;
        hasMore.value = currentTotal < total;

        if (hasMore.value && res.items.length > 0) {
          pageNum.value += 1;
        }
      } else {
        hasMore.value = false;
      }
    })
    .catch((error) => {
      console.error("获取列表失败:", error);
      loading.value = false;
      throw error;
    });
};

// 下拉刷新
onPullDownRefresh(() => {
  pageNum.value = 1;
  hasMore.value = true;

  Promise.all([
    getBannerList(),
    getHeadNewsFilter(),
    getHeadNewsList(activeFilter.value, false)
  ]).finally(() => {
    uni.stopPullDownRefresh();
  });
});

// 触底加载
onReachBottom(() => {
  if (hasMore.value && !loading.value) {
    getHeadNewsList(activeFilter.value, true);
  }
});

// 页面加载
onLoad(() => {
  getBannerList();
  getHeadNewsFilter();
  getHeadNewsList();
});
</script>
相关推荐
蓝帆傲亦2 小时前
支付宝小程序性能暴增秘籍:UniApp项目极限优化全攻略
小程序·uni-app
2501_9160088911 小时前
深入解析iOS机审4.3原理与混淆实战方法
android·java·开发语言·ios·小程序·uni-app·iphone
QT.qtqtqtqtqt12 小时前
uni-app小程序前端开发笔记(更新中)
前端·笔记·小程序·uni-app
喵喵虫1 天前
uniapp修改封装组件失败 styleIsolation
uni-app
游戏开发爱好者81 天前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
2501_915106321 天前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
2501_915106321 天前
使用 Sniffmaster TCP 抓包和 Wireshark 网络分析
网络协议·tcp/ip·ios·小程序·uni-app·wireshark·iphone
宠友信息2 天前
2025社交+IM及时通讯社区APP仿小红书小程序
java·spring boot·小程序·uni-app·web app
“负拾捌”2 天前
python + uniapp 结合腾讯云实现实时语音识别功能(WebSocket)
python·websocket·微信小程序·uni-app·大模型·腾讯云·语音识别
局外人LZ2 天前
Uniapp脚手架项目搭建,uniapp+vue3+uView pro+vite+pinia+sass
前端·uni-app·sass