主要參數:
● loading 是否加載中
● hasMore 是否有更多,根據分頁總數跟total判斷
● pageInfo 分頁參數信息{ pageNo: number, pageSize: number, total: number }
● loadMore 加載列表的函數,比如getList,該函數是一個async的Promise函數
● threshold 滾頂條距離底部多少px觸發到底事件
返回一個觸底加載的鈎子函數
import type { Ref } from 'vue'
import { onMounted, onUnmounted } from 'vue'
// 防抖函數
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): ((...args: Parameters<T>) => void) {
let timer: NodeJS.Timeout | null = null
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (timer)
clearTimeout(timer)
timer = setTimeout(() => {
// 使用 apply 綁定 this 上下文
fn.apply(this, args)
timer = null
}, delay)
}
}
/**
* 觸底加載更多
* @param {Ref<boolean>} loading 加載中
* @param {Ref<boolean>} hasMore 是否有更多
* @param {Ref<{ pageNo: number, pageSize: number, total: number }>} pageInfo 分頁信息
* @param {() => Promise<void>} loadMore 加載更多
* @param {number} threshold 閾值
* @returns {{ handleScroll: () => void }} 包含滾動處理函數的對象,handleScroll用於手動觸發滾動檢查
*/
export function useInfiniteScroll(
loading: Ref<boolean>,
hasMore: Ref<boolean>,
pageInfo: Ref<{ pageNo: number, pageSize: number, total: number }>,
loadMore: () => Promise<void>,
threshold: number = 10,
): { handleScroll: () => void } {
// 滾動函數
const handleScroll = () => {
if (loading.value || !hasMore.value)
return
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
if (scrollTop + clientHeight >= scrollHeight - threshold) {
pageInfo.value.pageNo++
loadMore()
}
}
// 防抖-性能優化
const onScroll = debounce(handleScroll, 200)
onMounted(() => {
window.addEventListener('scroll', onScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', onScroll)
})
return { handleScroll }
}
頁面使用:
<script setup lang='ts'>
import { useInfiniteScroll } from '@/hook/useInfiniteScroll'
import { getListAPI } from '@/api/model/common'
const loading = ref(false)
const hasMore = ref(true)
const list = ref([])
const pageInfo = ref({
pageNo: 1,
pageSize: 10,
total: 0,
})
async function getContentPage() {
try {
let params = {
pageNo: pageInfo.value.pageNo,
pageSize: pageInfo.value.pageSize,
}
loading.value = true
let res = await getListAPI(params)
list.value.push(...res.list)
pageInfo.value.total = res.total
// 每次查完都判斷一下是否還有下一頁
if (pageInfo.value.pageNo * pageInfo.value.pageSize >= pageInfo.value.total) {
hasMore.value = false
}
else {
hasMore.value = true
}
}
finally {
loading.value = false
}
}
// 觸底加載鈎子
useInfiniteScroll(loading, hasMore, pageInfo, getContentPage)
// 初始化
getContentPage()
</script>
由於loading, hasMore, pageInfo, getContentPage都是頁面傳入的,鈎子函數內部不用做任何更改,只需要關注頁面邏輯即可。