一、上拉加载与下拉刷新的核心区别
1.触发方式与交互目标
-
下拉刷新
- 触发动作:用户在页面顶部向下滑动一定距离后触发。
- 目标:重新加载最新数据(如新消息、动态更新),覆盖原有数据。
- 交互设计:通常伴随动画提示(如“刷新中...”),完成后恢复原位。
-
上拉加载
- 触发动作:用户在页面底部向上滑动至触底区域时触发。
- 目标:加载更多历史数据(如分页列表的下一页数据),追加到现有列表。
- 交互设计:底部显示加载提示(如“加载中...”或“没有更多数据”)
2、.数据操作逻辑
功能 | 数据操作 | 参数变化 |
---|---|---|
下拉刷新 | 清空旧数据,重新加载第一页数据 | page=1 ,重置页码 |
上拉加载 | 保留旧数据,追加下一页数据 | page++ ,递增页码 |
在 Uni-App 中实现 下拉刷新(下拉加载)和 上拉加载更多(上拉刷新)可以通过原生 API 和页面配置轻松实现。以下是具体实现步骤和代码示例:
二、下拉刷新(下拉加载)
1. 配置页面支持下拉刷新
在 manifest.json
的页面配置中,开启下拉刷新功能:
{
"pages": [
{
"path": "pages/index/index",
"style": {
"enablePullDownRefresh": true // 开启下拉刷新
"onReachBottomDistance": 100 //// 触底距离(单位:px)
}
}
]
}
2. 实现下拉刷新逻辑
在页面的 .vue
文件中,通过 onPullDownRefresh(
整个页面的刷新)
生命周期或事件监听实现:
<template>
<view class="content">
<!-- 页面内容 -->
<text>下拉刷新示例</text>
</view>
</template>
<script>
export default {
onPullDownRefresh() {
// 下拉刷新时触发的逻辑
this.refreshData();
},
methods: {
async refreshData() {
// 模拟数据刷新(例如请求接口)
await new Promise(resolve => setTimeout(resolve, 1000));
// 结束下拉刷新动画
uni.stopPullDownRefresh();
}
}
};
</script>
三、上拉加载更多(无限滚动)
1. 监听页面滚动到底部
可以通过 onReachBottom
(整个页面的刷新)生命周期或自定义滚动容器(如 scroll-view
)监听滚动事件。
方法 1:使用 onReachBottom
(简单场景)
<template>
<view class="content">
<!-- 页面内容 -->
<text>上拉加载更多示例</text>
</view>
</template>
<script>
export default {
onReachBottom() {
// 触发加载更多逻辑
this.loadMore();
},
methods: {
async loadMore() {
// 模拟加载更多数据
await new Promise(resolve => setTimeout(resolve, 1000));
// 更新数据列表
this.list = [...this.list, ...newData];
}
}
};
</script>
四、使用 scroll-view(
区域滚动,不会触发页面滚动)
自定义滚动(复杂场景)
当页面内容需要嵌套滚动时,使用 scroll-view
组件监听滚动到底部:
<<scroll-view
scroll-y
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<!-- 数据列表 -->
<view v-for="item in list">{{ item.title }}</view>
<!-- 加载提示 -->
<view v-if="isLoading">加载中...</view>
</scroll-view>
methods: {
onRefresh() { // 下拉刷新
this.isRefreshing = true;
this.page = 1;
this.getData(() => this.isRefreshing = false);
},
onLoadMore() { // 上拉加载
if (this.isLoading || this.isEnd) return;
this.isLoading = true;
this.page++;
this.getData(() => this.isLoading = false);
}
}
五、第三方组件库方案(企业级需求)
1. uView 组件
uView 2.0 是一款功能强大的UI框架,专为UniApp开发设计,提供了丰富的组件和工具,帮助开发者提升开发效率和UI设计水平。
快速集成封装好的列表组件:
npm install uview-ui
或在 HBuilderX 插件市场搜索 uView UI 直接安装
//main.js
import uView from 'uview-ui'
Vue.use(uView)
//uni.scss
@import '@/uni_modules/uview-ui/theme.scss';
//在 App.vue 的 <style> 标签首行添加:
@import "@/uni_modules/uview-ui/index.scss";
//修改 pages.json,添加规则 配置 easycom 自动引入组件
"easycom": {
"custom": {
"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
}
}
uListView
默认支持虚拟滚动,适用于渲染大量数据(如 1000+ 条目)
<template>
<view class="container">
<u-list
:height="listHeight"
@on-pull-down="onRefresh"
@on-reach-bottom="onLoadMore"
>
<u-list-item v-for="(item, index) in list" :key="index">
<view class="list-item">
{{ item.title }}
</view>
</u-list-item>
</u-list>
<!-- 下拉刷新提示 -->
<u-loadmore
:status="loading ? 'loading' : 'loadmore'"
:loading-text="'正在加载...'"
:loadmore-text="'上拉加载更多'"
:nomore-text="'没有更多了'"
/>
</view>
</template>
<script>
export default {
data() {
return {
list: [],
page: 1,
loading: false,
listHeight: 600 // 根据页面布局设置高度
};
},
methods: {
// 模拟数据请求
async fetchData(page) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: Array.from({ length: 10 }, (_, i) => ({
title: `第${page}页第${i + 1}条数据`
}))
});
}, 500);
});
},
// 下拉刷新
async onRefresh() {
this.loading = true;
const res = await this.fetchData(1);
this.list = res.data;
this.page = 1;
this.loading = false;
},
// 上拉加载
async onLoadMore() {
if (this.loading) return;
this.loading = true;
this.page++;
const res = await this.fetchData(this.page);
this.list = [...this.list, ...res.data];
this.loading = false;
}
}
};
</script>
<style>
.container {
height: 100vh;
}
.list-item {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
</style>
2. mescroll-nui
引入插件实现高级滚动控制:
import Mescroll from 'mescroll-nui';
export default {
components: { Mescroll },
mescroll: new Mescroll({
down: { use: true }, // 启用下拉刷新
up: { use: true } // 启用上拉加载
})
}
六、完整实现代码(基于 uni-app 原生事件方案)
1.pages.json
配置
{
"path": "pages/list/list",
"style": {
"enablePullDownRefresh": true, // 开启下拉刷新
"onReachBottomDistance": 100 // 触底触发距离
}
}
2.页面逻辑实现
<template>
<view class="container">
<!-- 下拉刷新提示 -->
<view v-if="isRefreshing" class="refresh-tip">正在刷新...</view>
<!-- 数据列表 -->
<view v-for="item in list" :key="item.id" class="item">
{{ item.title }}
</view>
<!-- 上拉加载提示 -->
<view class="load-more-tip">
{{ loadStatus === 'loading' ? '加载中...' :
loadStatus === 'noMore' ? '没有更多数据' : '' }}
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [], // 数据列表
page: 1, // 当前页码
pageSize: 10, // 每页条数
total: 0, // 总数据量
isRefreshing: false,
loadStatus: 'more' // loading | noMore
}
},
onLoad() {
this.getList()
},
// 下拉刷新触发
onPullDownRefresh() {
this.page = 1
this.getList(() => {
uni.stopPullDownRefresh() // 必须停止刷新
})
},
// 上拉加载触发
onReachBottom() {
if (this.loadStatus !== 'more') return
this.page++
this.getList()
},
methods: {
getList(callback) {
this.loadStatus = 'loading'
// 模拟接口请求
setTimeout(() => {
const newData = Array.from({ length: this.pageSize }, (_, i) => ({
id: i + (this.page - 1) * this.pageSize,
title: `项目 ${i + (this.page - 1) * this.pageSize}`
}))
// 下拉刷新时替换数据
if (this.page === 1) {
this.list = newData
} else {
this.list = [...this.list, ...newData]
}
// 模拟总数据量判断
this.total = 30
this.loadStatus = this.page * this.pageSize >= this.total ? 'noMore' : 'more'
callback?.()
}, 1000)
}
}
}
</script>
<style>
.container { padding: 20rpx; }
.item { padding: 30rpx; border-bottom: 1px solid #eee; }
.refresh-tip, .load-more-tip { text-align: center; padding: 20rpx; color: #666; }
</style>
3.核心逻辑说明
-
数据加载控制
- 下拉刷新重置
page=1
,清空旧数据 - 上拉加载通过
page++
实现分页 - 通过
page*pageSize >= total
判断数据是否加载完毕
- 下拉刷新重置
-
交互提示优化
- 下拉时显示
refresh-tip
提示 - 底部显示
loading/noMore
状态提示
- 下拉时显示
-
强制停止刷新
必须调用uni.stopPullDownRefresh()
关闭动画
4.扩展建议
- 接口防抖:在
onReachBottom
中添加防抖避免重复请求 - 虚拟列表:超长列表建议使用
uni-virtual-list
组件优化性能 - 错误处理:接口请求失败时重置
loadStatus
状态
七、基于 scroll-view
的完整实现方案(Vue3 组合式 API)
<template>
<!-- 固定头部(如有) -->
<view class="header">列表头部</view>
<view>
<!-- scroll-view 核心区域 -->
<scroll-view
scroll-y
:refresher-enabled="true"
:refresher-triggered="triggered"
:style="{ height: scrollHeight + 'px' }"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
>
<!-- 下拉刷新动画 -->
<view class="refresh-tip">
{{ isRefreshing ? '刷新中...' : '下拉刷新' }}
</view>
<!-- 数据列表 -->
<view v-for="item in list" :key="item.id" class="item">{{ item.title }}</view>
<!-- 上拉加载提示 -->
<view class="load-more-tip">
{{ loadStatus === 'loading' ? '加载中...' :
loadStatus === 'noMore' ? '没有更多数据' : '' }}
</view>
</scroll-view>
</view>
</template>
<style>
.header { height: 80rpx; line-height: 80rpx; text-align: center; }
.scroll-view { background: #f5f5f5; }
.refresh-tip, .load-more-tip { padding: 20rpx; text-align: center; color: #666; }
.item { padding: 30rpx; background: white; margin-bottom: 10rpx; }
</style>
核心要点
- 必须设置固定高度(动态计算屏幕剩余高度)
refresher-enabled
需保持true
以启用下拉
<script setup>
import { ref, onMounted } from 'vue'
// 动态高度计算
const scrollHeight = ref(0)
const list = ref([]) // 数据列表
const page = ref(1) // 当前页码
const pageSize = 10 // 每页条数
const total = ref(30) // 总数据量
const triggered = ref(false) // 下拉刷新触发状态
const loadStatus = ref('more')// 加载状态(more/loading/noMore)
const isRefreshing = ref(false)
// 初始化计算高度
onMounted(() => {
uni.getSystemInfo({
success: (res) => {
// 扣除头部高度(示例中80rpx≈40px)
scrollHeight.value = res.windowHeight - 40
}
})
})
// 下拉刷新
const handleRefresh = () => {
if (isRefreshing.value) return
isRefreshing.value = true
triggered.value = true // 必须手动激活状态
page.value = 1
loadData(() => {
triggered.value = false // 完成后必须重置
isRefreshing.value = false
})
}
// 上拉加载
const handleLoadMore = () => {
if (loadStatus.value !== 'more') return
loadStatus.value = 'loading'
page.value++
loadData()
}
// 数据加载(模拟接口)
const loadData = (callback) => {
setTimeout(() => {
const newData = Array.from({ length: pageSize }, (_, i) => ({
id: i + (page.value - 1) * pageSize,
title: `Item ${i + (page.value - 1) * pageSize}`
}))
// 刷新时替换数据,加载时追加
if (page.value === 1) {
list.value = newData
} else {
list.value = [...list.value, ...newData]
}
// 判断数据是否加载完毕
loadStatus.value = page.value * pageSize >= total.value ? 'noMore' : 'more'
callback?.()
}, 1000)
}
</script>
1.关键注意事项
-
高度动态计算
- 需根据页面布局(如固定导航栏)调整
scrollHeight
- 使用
uni.getSystemInfo
获取精确窗口高度
- 需根据页面布局(如固定导航栏)调整
-
状态强制重置
- 下拉刷新后必须执行
triggered.value = false
,否则动画无法关闭 - 网络请求失败时需回退
page
和重置状态
- 下拉刷新后必须执行
-
防抖处理
- 在
handleLoadMore
中添加防抖避免重复触发
- 在
let timer = null
const handleLoadMore = () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
// 加载逻辑
}, 300)
}