詳解Vue如何手寫一個虛擬列表
前言
何為虛擬列表?
回答這個問題先想一下,對于返回的幾千上萬條列表數(shù)據(jù),作為前端會如何渲染?
分頁唄,最開始想到的就是分頁。但是,萬一接口沒做分頁呢?好,就算后端做分頁了,那么用戶在瀏覽到第幾千條、第幾萬條數(shù)據(jù)時前端難道要把這第幾千條、幾萬條的DOM都渲染出來嗎?顯然是很消耗性能的(畢竟如此多的DOM,而且每個DOM內(nèi)部都有其他樣式細節(jié))如何優(yōu)化?此時就可以用到虛擬列表了
虛擬列表是一種優(yōu)化長列表渲染的技術,它可以在保持流暢性的同時,渲染大量的數(shù)據(jù)。
在傳統(tǒng)的列表渲染中,如果列表非常長,會導致渲染時間過長(前面所說的會有幾千幾萬個DOM),頁面卡頓,用戶體驗變得非常差。而虛擬列表則是只渲染可見區(qū)域內(nèi)的數(shù)據(jù),而非全部渲染,這樣就可以大大提高渲染效率,保持頁面流暢性
常見場景
商品列表、社交列表...(暫時想到這兩個)
手寫虛擬列表
進行虛擬列表的實現(xiàn)之前先搞清楚一個問題:
是什么造成了這么多數(shù)據(jù)在渲染時產(chǎn)生卡頓?是數(shù)據(jù)數(shù)量太多引起的嗎?
準確來說,應該是是由于要渲染的DOM太多造成的卡頓。數(shù)據(jù)本身只是數(shù)據(jù),對于拿到的幾千上萬條數(shù)據(jù)它本身的大小對于內(nèi)存來說只能說是冰山一角吧
虛擬列表的原理
數(shù)據(jù)我還是這么多數(shù)據(jù),但是我不一次性渲染這么多數(shù)據(jù),我只渲染其中的一小部分(比如十條),這樣當用戶滾動的時候就重復變化這一小部分的DOM的渲染效果。這樣就能從原先幾千上萬個DOM變成現(xiàn)在的十個,減少了一個量級,減輕了渲染的壓力。而這一小部分顯示的區(qū)域暫稱之為視圖區(qū)域吧
審查元素,大致的效果如圖:

賊長的這部分是所有的數(shù)據(jù)的盒子所占的大小,但是每次只控制小部分數(shù)據(jù)在上面的盒子進行顯示
虛擬列表的實現(xiàn)
怎么實現(xiàn)上面的效果?這里用到了固定定位和絕對定位
<script lang='ts' setup>
type Item = {
id: number
name: string
}
const allListData = ref<Item[]>([]) // 存放十萬條數(shù)據(jù)
const itemHeight = ref(40) // 每一條(項)的高度
const count = ref(10) // 一屏展示幾條數(shù)據(jù)
const startIndex = ref(0) // 開始位置的索引
const endIndex = ref(10) // 結(jié)束位置的索引
const topVal = ref(0) // 父元素滾動位置
// 計算展示的列表
const showListData = computed(() => allListData.value.slice(startIndex.value, endIndex.value))
// 模擬十萬條數(shù)據(jù)
const getData = async () => {
for (let i = 0; i < 10000; i++) {
allListData.value.push({ name: `第${i}條數(shù)據(jù)`, id: i })
}
}
// 初始化加載
onMounted(() => {
getData()
})
// 虛擬列表視口區(qū)域的組件實例
const viewport = ref<HTMLDivElement>()
const handleScroll = () => {
console.log('滾動了')
// 非空判斷
if (!viewport.value) return
// 獲取滾動距離(這里通過組件實例獲取的,當然也可以通過在該事件的事件參數(shù)中拿到)
const scrollTop = viewport.value.scrollTop
// 計算起始下標和結(jié)束下標,用于 computed 計算
startIndex.value = Math.floor(scrollTop / itemHeight.value)
endIndex.value = startIndex.value + count.value
// 動態(tài)更改定位的 top 值,動態(tài)展示相應內(nèi)容
topVal.value = viewport.value.scrollTop
}
</script>
<template>
<!-- 虛擬列表容器 -->
<div
class="viewport"
ref="viewport"
:style="{ height: 10條數(shù)據(jù)撐開的高度 }"
>
<!-- 占位元素,高度為所有的數(shù)據(jù)的總高度 -->
<div
class="placeholder"
:style="{ height:itemHeight * count + 'px' }"
></div>
<!-- 視圖區(qū),展示10條數(shù)據(jù),注意其定位的top值是變化的 -->
<div class="list" :style="{ top: topVal + 'px' }">
<!-- 每一條(項)數(shù)據(jù) -->
<div
v-for="item in showListData"
:key="item.id"
class="item"
:style="{ height: itemHeight + 'px' }"
>
{{ item.name }}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.viewport {
box-sizing: border-box;
width: 240px;
border: solid 1px #000000;
// 開啟滾動條
overflow-y: auto;
// 開啟相對定位
position: relative;
.list {
width: 100%;
height: auto;
// 搭配使用絕對定位
position: absolute;
top: 0;
left: 0;
.item {
box-sizing: border-box;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
// 隔行變色
&:nth-child(even) {
background: skyblue;
}
&:nth-child(odd) {
background: #fff;
}
}
}
}
</style>
一開始視圖區(qū)域的top值為0,剛好在最頂端,監(jiān)聽滾動事件,當滾動時實時改變top值以及該區(qū)域內(nèi)渲染的數(shù)據(jù),從而實現(xiàn)了虛擬列表
視圖區(qū)域的top值為什么要動態(tài)監(jiān)聽?試想一下,現(xiàn)在所有數(shù)據(jù)的總高度為1000px,視圖區(qū)域為100px,當用戶滾動到500px時,如果視圖區(qū)域的top值不動態(tài)綁定,那么視圖區(qū)域還停留在top=0(也就是最頂端處),那肯定就看不到最新的視圖了
到此這篇關于詳解Vue如何手寫一個虛擬列表的文章就介紹到這了,更多相關Vue虛擬列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
elementUI表格多選框this.$refs.xxx.toggleRowSelection無效問題
這篇文章主要介紹了elementUI表格多選框this.$refs.xxx.toggleRowSelection無效問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
Vue中 v-if/v-show/插值表達式導致閃現(xiàn)的原因及解決辦法
在開發(fā)過程中經(jīng)常會發(fā)現(xiàn)當頁面明明不應該出現(xiàn)的元素或內(nèi)容會閃現(xiàn)一下然后消失,這篇文章給大家分享Vue中 v-if/v-show/插值表達式導致閃現(xiàn)的原因及解決辦法,一起看看吧2018-10-10
vue+koa2實現(xiàn)session、token登陸狀態(tài)驗證的示例
這篇文章主要介紹了vue+koa2實現(xiàn)session、token登陸狀態(tài)驗證的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08

