Live2d Test Env

vue3写一个触底加载hook

天行健,地势坤 -- 《周易·象传》

背景:在一个滚动容器下实现触底加载更多的函数,由于此模块使用的场景较多,因而自己实现了一个滚动加载的hook(顺带尝试了下hook)

需求:左侧tab切换时,右侧聊天历史tab要清空并重新请求;右侧触底时,要触发分页请求(如果有);使用dayjs中的fromnow方法以精确体现用户在今日对话中的记录(略)。

  1. 基础知识引入

如何让一个容器触发滚动态:

一句话总结:容器的内容物高度大于容器本身高度且容器的css样式为overflow:auto时触发。

如何给一个容器添加滚动事件:

element.addEventListener('scroll', scrollFn)

  1. 代码实现

useBottomScroll.ts


import { nextTick, onMounted, onUnmounted, ref } from 'vue'

/**
 * elementRef : 传入的dom实例
 * onReachBottomFn :滚动加载函数
 * reachBottomDistance : 距离底部多少距离时触发
 */

function useScrollBottom(elementRef, onReachBottomFn, reachBottomDistance = 100) {
  const isReachBottom = ref(false)
  const onScroll = async () => {
    const scrollTop = elementRef.value.scrollTop
    const scrollHeight = elementRef.value.scrollHeight
    const clientHeight = elementRef.value.clientHeight
    const currentHeight = scrollTop + clientHeight
    const isAtBottom = currentHeight + reachBottomDistance >= scrollHeight
    if (isAtBottom && !isReachBottom.value) {
      // 调用传入的触底触发函数  bool 值代表是否还有更多数据
      const isEnd = await onReachBottomFn()
      isReachBottom.value = isEnd
    }
  }

  onMounted(async () => {
    nextTick(async () => {
      elementRef.value.addEventListener('scroll', onScroll)
      // 默认触发一次分页接口
      await onReachBottomFn()
    })
  })

  onUnmounted(() => {
    elementRef.value && elementRef.value.removeEventListener('scroll', onScroll)
  })

  return {
    isReachBottom
  }
}

export default useScrollBottom

这里最重要的有两点,

(1). 判断滚动的时机

滚动的时机以isAtBottom变量划定,即当前位置加上预设距离是否大于等于元素总高,如果是,则为true

(2). 修改滚动的条件:isReachBottom

修改滚动的条件isReachBottom则由调用方提供,本质上是当前页数current*当前条数size 是否大于等于 总条数total,每次在条件满足触发加载后都会重新赋值isReachBottom

  1. 业务调用:

<div ref="todayRef" class="scroll-container">
	<!--balabala-->
</div>

import useScrollBottom from '@/hooks/useBottomScroll.ts'

// 监听左侧tag页,一旦切换即重新触发
watch(
  () => currentTag.value,
  (n) => {
    todayBool.value = false
    todayList.value = []
    todayPage.value.current = 0
    loadMoreDataFn()
  },
  {
    immediate: true
  }
)

const todayList = ref([])
const todayRef = ref(null)
// 这里最重要的就是current  size默认写死,total可以扔掉
const todayPage = ref({ size: 10, current: 0, total: 0 } )
const { isReachBottom: todayBool } = useScrollBottom(todayRef, loadMoreDataFn)


const loadMoreDataFn= async () => {
  todayPage.value.current += 1
  const params = {
    current: todayPage.value.current,
    size: 10, 
  }
  const res = await getTodayStoryListApi(params)
  if (Array.isArray(res.records)) {
    todayList.value = [...todayList.value, ...res.records]
  }
 // 返回的isEnd参数-》params.current * params.size >= Number(res.total))
  return new Promise((c) => c(params.current * params.size >= Number(res.total)))
}
  1. 总结与分析

滚动加载最重要的其实就是current参数,不论tab页的切换亦或者触底后是否继续访问接口都与此息息相关,笔者原本准备将请求函数写在hook内部,但是如此一来就丧失了一些灵活性——固定输入,并且如果有时候在外部需要一些返回值时,又不方便提取(也可能是刚开始用hook),因而就将loadFn写在了外部,而触发的时机本质上有两个点:1,是否接近或者等于约定值,2. 外部传入的isEnd值是否为false,而isEnd又是外部传入的current*size >= total返回的布尔值,一套流程下来也就形成了闭环。

  1. 不足

之前仿照别的项目组时的做法,想在loadMoreDataFn内赋一个默认值isRefresh 来判断是否是否切换了左侧tab,但是测试后发现无效,猜测可能是在hook内部调用

const isEnd = await onReachBottomFn()
时没能将参数传递进去,这里百思不得其解,因而不得不在业务模块使用watch手动控制current和todayList的值,如有知道解决办法的也欢迎指教,不胜感激

以上。

posted @ 2024-08-27 11:55  致爱丽丝  阅读(135)  评论(0编辑  收藏  举报