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运行到鸿蒙没有反应或者一直运行方式:打开 undefined, 导入 dist\dev\.app-harmony 运行
华为·uni-app·harmonyos
tianxinw2 小时前
uniapp x + vue3 实现echarts图表
前端·uni-app·vue·echarts
2501_9159184114 小时前
只有 Flutter IPA 文件,通过多工具组合完成有效混淆与保护
android·flutter·ios·小程序·uni-app·iphone·webview
2501_9159184120 小时前
除了 Perfdog,如何在 Windows 环境中完成 iOS App 的性能测试工作
android·ios·小程序·https·uni-app·iphone·webview
丢,捞仔21 小时前
uni-app上架应用添加权限提示框
前端·javascript·uni-app
雪芽蓝域zzs1 天前
uniapp富文本rich-text
uni-app
Qlittleboy1 天前
uniAPP报错:v-for 暂不支持循环数据: (env: Windows,mp,1.06.2307260; lib: 3.12.0)
uni-app
曾帅1681 天前
uniapp安卓启动图
android·opencv·uni-app
m0_740859621 天前
解决uniapp折叠面板报错this.collapse.onChange is not a function
uni-app