Vue3.2实现一个虚拟列表Hooks

335 阅读1分钟

项目中有很多地方需要用到大数据展示,想到了用虚拟列表去完成。(Intersectionobserver()也能实现效果,之后会用这个API去完成一个虚拟列表) 下面是源码 跟使用方法。 喜欢的帮我点个赞谢谢 [jcode](https://code.juejin.cn/pen/7226649346240938036)

import { computed, nextTick, reactive, Ref, watchEffect } from 'vue'

interface VirtualListProps<T> {
  listData: Ref<T[]>
  scrollBox: Ref<HTMLElement | null | undefined>
  items: Ref<HTMLElement | null | undefined>
}

interface VirtualListState {
  dataList: any[]
  itemBoxHeight: number
  itemNum: number
  startIndex: number
}

export function useVirtualList<T>(props: VirtualListProps<T>) {
  const { listData, scrollBox, items } = props

  const state: VirtualListState = reactive({
    dataList: [],
    itemBoxHeight: 0,
    itemNum: 1,
    startIndex: 0
  })

  // 计算虚拟列表项
  const virtualList = computed(() => {
    let endIndex = state.startIndex + state.itemNum
    if (endIndex >= listData.value.length) endIndex = listData.value.length
    return listData.value.slice(state.startIndex, endIndex)
  })

  // 监听滚动事件
  const handleScroll = () => {
    const curScrollTop: any = scrollBox.value?.scrollTop
    if (curScrollTop > state.itemBoxHeight) {
      const index = ~~(scrollBox.value!.scrollTop / state.itemBoxHeight)
      items.value?.style.setProperty('padding-top', `${index * state.itemBoxHeight}px`)
      state.startIndex = index
    } else {
      items.value?.style.setProperty('padding-top', '0px')
      state.startIndex = 0
    }
  }

  watchEffect(() => {
    if (listData.value.length > 0) {
      nextTick(() => {
        // 计算每行高度
        state.itemBoxHeight = (items.value?.children[0] as HTMLElement)?.offsetHeight //计算屏幕内能显示的行数   +5是防止下拉过快出现白屏
        state.itemNum = ~~(scrollBox.value!.clientHeight / state.itemBoxHeight) + 5 // 设置列表总高度
        const listHeight = state.itemBoxHeight * listData.value.length
        items.value?.style.setProperty('height', `${listHeight}px`)
      })
    }
  })

  return {
    state,
    virtualList,
    handleScroll,
    scrollBox,
    items
  }
}

<script setup lang="ts">
import { useVirtualList } from '@/hooks/event/useVirtualList'
const scrollBox: Ref<HTMLElement | null> = ref(null)
const items: Ref<HTMLElement | null | undefined> = ref(null)
const { virtualList, handleScroll } = useVirtualList({
  listData,
  scrollBox,
  items
})
</script>
 <div @scroll="handleScroll" ref="scrollBox">
      <div ref="items" >
     </div>
 </div