動態

詳情 返回 返回

vue3封裝一個頁面級的滾動條觸底加載函數 - 動態 詳情

主要參數:
● loading 是否加載中
● hasMore 是否有更多,根據分頁總數跟total判斷
● pageInfo 分頁參數信息{ pageNo: number, pageSize: number, total: number }
● loadMore 加載列表的函數,比如getList,該函數是一個asyncPromise函數
● 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都是頁面傳入的,鈎子函數內部不用做任何更改,只需要關注頁面邏輯即可。

user avatar wxp686 頭像
點贊 1 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.