前言
书接上文,前篇文章介绍了如何手写一个虚拟列表,这篇文章就介绍一下如何偷懒使用现有的三方工具实现
vueUse方案实现:
采用vueUse
中的useVirtualList
方法同样可以实现虚拟列表(相当于是使用一个封装好的方法)
步骤:
- 装包
npm i @vueuse/core
- 导入
useVirtualList
方法
xml
<script setup lang="ts">
import { useVirtualList } from '@vueuse/core'
</script>
如何使用?
在使用useVirtualList
方法时需要传入两个参数,一个是数据源(就是你那十万条数据),一个是配置项(配置每项的高度)
在配置项中配置的高度一定要和模板中渲染的每一项的高度相同(即配置项的高度要与css
中的高度保持一致)
这个方法返回三个字段,一个处理后的数据源,两个依赖项,这两个依赖项需要绑定到视图区域和占位盒子身上,从而实现虚拟列表的效果
手动添加类名,然后审查元素:
xml
<template>
<div v-bind="containerProps" style="height: 500px" class="container">
<div v-bind="wrapperProps" class="slot">
<div v-for="item in list" :key="item.data.id" style="height: 40px">
Row: {{ item.data }}
</div>
</div>
</div>
</template>
大致的效果就是虚拟列表的div
包裹着占位的div
,占位的div
内进行渲染。当然,样式自己就根据需要添加即可
完整代码:
typescript
<script setup lang="ts">
import { useVirtualList } from '@vueuse/core'
import { onMounted, ref } from 'vue'
type Item = {
id: number
name: string
}
// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])
// 获取十万条数据
const getData = () => {
for (let i = 0; i < 10000; i++) {
allListData.value.push({ name: `第${i}条数据`, id: i })
}
}
onMounted(() => {
getData()
})
const { list, containerProps, wrapperProps } = useVirtualList(allListData, {
// 此处配置的高度一定要和31行渲染时每一行的高度相同
itemHeight: 40
})
</script>
<template>
<div v-bind="containerProps" style="height: 500px" class="container">
<div v-bind="wrapperProps" class="slot">
<div v-for="item in list" :key="item.data.id" style="height: 40px">
Row: {{ item.data }}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.container {
border: 1px solid black;
}
</style>
同时注意,将原始数据源传给useVirtualList
之后返回的list
是经过封装过的,想这里原本数据的每一项的结构是{ name:'', id:xx }
,经过处理后,再想访问name
或者id
,就得多访问一层data
,即list.data.name / list.data.id
完整代码:
typescript
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
type Item = {
id: number
name: string
}
// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])
// 获取十万条数据
const getData = () => {
for (let i = 0; i < 10000; i++) {
allListData.value.push({ name: `第${i}条数据`, id: i })
}
}
onMounted(() => {
getData()
})
// 每一项的高度,比如 40 像素
const itemHeight = ref(40)
</script>
<template>
<RecycleScroller
class="viewport"
:items="allListData"
:item-size="itemHeight"
key-field="id"
v-slot="{ item }"
item-class="item"
>
{{ item.name }}
</RecycleScroller>
</template>
<style scoped lang="scss">
.viewport {
box-sizing: border-box;
width: 240px;
height: 400px;
border: solid 1px #000000;
// 开启滚动条
overflow-y: auto;
<!-- 由于模板中使用了 item-class 属性将渲染的每一项的类名改为 item,所以这里开启深层次的样式修改-->
:deep(.item) {
box-sizing: border-box;
width: 100%;
height: 40px; // 高度设置得和 item-size 属性的值一致。表示每一项的高度
display: flex;
justify-content: center;
align-items: center;
// 隔行变色
&:nth-child(even) {
background: skyblue;
}
&:nth-child(odd) {
background: red;
}
}
}
</style>
实现效果:
审查一下元素:
其实可以发现底层实现原理和之前手写的部分差不多,都是最外层一个虚拟列表盒子,内层放置一个具有所有数据的高度的盒子,然后再设置一个视图层进行滚动。但是使用封装好的东西可以不用自己设置定位以及更新top
值,而且其实它的文档里还有其他很多的属性和事件,拓展性强。还支持每一项的高度可以不同(可能在那种评论区用得多,毕竟每条评论的内容不一定都相同)
报错问题:
由于使用的是 Vue3 + TS,所以在导入的时候会有类型报错,虽然不影响功能
尝试下载提示中的包文件,但是竟然显示找不到了,可能是在@types
中没有这个包的声明文件吧