虛擬表格實現全解析

在數據展示越來越復雜的今天,大量數據的渲染就像是“滿漢全席”——如果把所有菜肴一次性擺上桌,既浪費資源也讓人眼花繚亂。幸運的是,我們有兩種選擇:

  • 自己動手:通過二次封裝 Element Plus 的表格組件,實現虛擬滾動,只渲染用戶視野中的數據,確保性能絲滑。
  • 直接用貨:直接使用 Element Plus 封裝好的虛擬表格組件,省時省力,穩穩地解決問題

本文將主要講解如何實現自己的虛擬表格,并對整個實現思路進行深度解析,同時友好地告訴你:如果懶得折騰,Element Plus 的組件已經為你準備好了完美方案!

1. 為什么需要虛擬表格?

當數據量較小時(例如 100 條以內),直接渲染 <el-table> 完全沒有問題。但一旦數據量飆升到數千或上萬條時,瀏覽器就可能因為渲染過多 DOM 節點而變得像卡住的老爺車。解決方案很簡單:虛擬滾動。虛擬滾動技術只渲染當前可見區域的數據,而把其余數據“藏”起來,直到滾動時才動態加載,這就像只上桌當下你需要的菜,其余的保持在廚房中等待叫單

2. 實現思路與系統架構

我們采用基于 Element Plus 的二次封裝方式,核心思路如下:

  • 頁面組件 index.vue
    負責生成數據并調用接口,將數據傳遞給虛擬表格組件。

  • 虛擬表格組件 VirtualTable.vue
    在 Element Plus 的 <el-table> 基礎上封裝,接入自定義的虛擬滾動邏輯,動態調整渲染數據范圍。

  • 核心邏輯 useTakeVirtualScroll.ts
    這是“魔術師”所在,通過監聽滾動和數據變化,根據當前視口計算出需要展示的數據區間,僅渲染這一部分數據,從而大幅提升性能。

溫馨提示:雖然本文詳細介紹了如何實現虛擬表格,但如果你只是想快速搭建產品,也可以直接使用 Element Plus 封裝好的虛擬表格組件,它已經集成了很多優化功能,無需額外開發!

3. 代碼實現詳解

3.1 頁面組件 index.vue

這個組件負責生成數據并模擬接口請求,然后將數據傳遞給我們的虛擬表格組件。看代碼就知道,點擊按鈕就像是向廚房下單,數據開始滾滾而來:

<template><div><el-button type="primary" @click="handleGenerateData(100)" :disabled="loading">生成100條數據</el-button><el-button type="primary" @click="handleGenerateData(10000)" :disabled="loading">生成10000條數據</el-button><el-text type="danger">超過100條數據后,開啟虛擬滾動</el-text></div><div class="virtual-table"><Table :data="data" :columns="column" :loading="loading" height="100%"><template #operation><el-link type="primary">編輯</el-link></template></Table></div>
</template><script setup lang="ts">
import Table from '@/components/VirtualTable/index.vue'
import { column } from './ts/column'
import axios from 'axios'
import { ref } from 'vue'
const data = ref([])const loading = ref(false)
// 模擬接口請求
function handleGenerateData(num: number) {loading.value = trueaxios.post('http://localhost:8050/generateData', { num }).then(res => {if (res.data.message === 'success') {data.value = res.data.data}}).finally(() => {loading.value = false})
}
</script><style scoped>
.virtual-table {width: 100%;height: calc(100% - 32px);padding-top: 10px;box-sizing: border-box;
}
</style>

3.2 虛擬表格組件 VirtualTable.vue

在這個組件中,我們利用 Element Plus 的 <el-table>,并引入 useTakeVirtualScroll 鉤子來實現虛擬滾動。簡而言之,它只負責展示當前可見的數據:

<template><el-table :data="filterData" v-loading="loading" v-bind="$attrs" @scroll="handleScroll"><el-table-column v-for="column in columns" :key="column.prop" v-bind="column"><template v-if="column.slot" #default="{ row }"><slot :name="column.slot" :row="row" /></template></el-table-column></el-table>
</template><script setup lang="ts">
import { computed } from 'vue'
import type { PropType } from 'vue'
import type { Column } from '@/views/VirtualTable/ts/column'
import { useTakeVirtualScroll } from '@/hooks/useTavkeVirtualScroll'
const props = defineProps({data: {type: Array,required: true,default: () => []},columns: {type: Array as PropType<Column[]>,required: true,default: () => []},loading: {type: Boolean,default: false},// 限制多少條后開啟虛擬滾動limit: {type: Number,default: 100}
})
const data = computed(() => props.data)
const { filterData, handleScroll } = useTakeVirtualScroll(data, props.limit)</script><style scoped>
::v-deep(.el-scrollbar__view .el-table__body) {position: sticky;top: 0;left: 0;
}
</style>

3.3 核心邏輯:虛擬滾動鉤子 useTakeVirtualScroll.ts

這部分代碼正是“幕后黑手”,它負責監聽滾動事件和數據變化,根據當前滾動位置計算出需要展示的數據區間。代碼精妙地保證了只渲染用戶可見部分:

import { ref, watch, nextTick, computed } from 'vue'
import { useEventListener, useDebounceFn } from '@vueuse/core'
import type { Ref } from 'vue'type FunctionType = (data: Ref<any[]>,limit: number,
) => { filterData: Ref<any[]>; handleScroll: (data: { scrollTop: number }) => void }export const useTakeVirtualScroll: FunctionType = (data, limit) => {const startIndex = ref(0) // 起始索引const endIndex = ref(0) // 結束索引const rowHeight = ref(42) // 行高// 計算過濾后的數據const filterData = computed(() => data.value.slice(startIndex.value, endIndex.value))// 監聽數據變化watch(data, async () => {const { tableView, virtualScrollView, scrollbarView } = getElement()if (data.value.length) {tableView.scrollTo(0, 0)// 如果數據的長度大于限制的長度,則初始化虛擬滾動if (data.value.length > limit) {await nextTick()initVirtualScroll()return} else {startIndex.value = 0endIndex.value = data.value.length}}console.log(virtualScrollView)// 如果數據的長度小于限制的長度,有虛擬滾動元素則移除if (virtualScrollView) {scrollbarView.removeChild(virtualScrollView)}})// 初始化虛擬滾動function initVirtualScroll() {// 如果沒有超出限制,就不進行虛擬滾動if (data.value.length <= limit) returnconst { tableView, virtualScrollView, scrollbarView } = getElement()const tableRow = scrollbarView.querySelector('.el-table__row') as HTMLElement // 獲取表格行rowHeight.value = tableRow?.clientHeight || 42 // 獲取表格行高const tableViewHeight = tableView?.clientHeight // 獲取表格可視窗口的高度const virtualScrollHeight = rowHeight.value * data.value.length // 根據數組的長度來計算表格需要滾動的虛擬高度// 計算當前滾動到的行索引以及可視行數setIndex(Math.floor(tableView.scrollTop / rowHeight.value), Math.ceil(tableViewHeight / rowHeight.value))// 如果存在虛擬滾動視圖,則更新高度if (virtualScrollView) {virtualScrollView.style.height = `${virtualScrollHeight - tableViewHeight}px`return}// 創建一個元素const fragment = document.createDocumentFragment()// 創建一個虛擬高度的元素const virtualScrollViewElement = document.createElement('div')virtualScrollViewElement.classList.add('virtual-scroll-view')// 設置虛擬高度的元素高度需要減去表格的可視化的高度virtualScrollViewElement.style.height = `${virtualScrollHeight - tableViewHeight}px`fragment.appendChild(virtualScrollViewElement)// 將虛擬高度的元素添加到表格中scrollbarView.appendChild(fragment)}// 處理滾動function handleScroll({ scrollTop }: { scrollTop: number }) {if (data.value.length <= limit) {return}const { tableView } = getElement()const tableViewHeight = tableView?.clientHeight // 獲取表格可視窗口的高度// 計算當前滾動到的行索引以及可視行數setIndex(Math.floor(scrollTop / rowHeight.value), Math.ceil(tableViewHeight / rowHeight.value))}// 獲取想要的元素function getElement() {const tableView = document.querySelector('.el-scrollbar__wrap') as HTMLElement // 獲取滾動容器const scrollbarView = document.querySelector('.el-scrollbar__view') as HTMLElement // 獲取滾動視圖const virtualScrollView = scrollbarView.querySelector('.virtual-scroll-view') as HTMLElement // 獲取虛擬滾動視圖return { tableView, virtualScrollView, scrollbarView }}// 設置索引function setIndex(start: number, end: number) {startIndex.value = Math.max(0, start)endIndex.value = Math.min(data.value.length, start + end)}const debouncedFn = useDebounceFn(initVirtualScroll, 100)useEventListener(window, 'resize', debouncedFn)return { filterData, handleScroll }
}

細解析:

  • 數據截取策略

    • 核心變量startIndexendIndex 分別定義了當前可見數據的起始與結束位置;rowHeight 則表示每一行的高度。
    • filterData 計算屬性:借助 Vue 的響應式特性,filterData 始終返回 data 數組中從 startIndexendIndex 的部分,從而保證頁面只渲染用戶當前能看到的數據。
  • 數據監聽與初始化

    • watch(data, async () => { ... }):每當數據發生變化時,先等待 DOM 更新(通過 nextTick()),再判斷數據量是否超過設定閾值。
    • 若數據量超過 limit,則調用 initVirtualScroll() 進行初始化;否則直接顯示全部數據。
    • 這種機制就像在超市里:當貨架上的商品數量不多時,顧客可以一目了然;而一旦商品過多,則分區促銷,只展示一部分熱銷品。
  • 初始化虛擬滾動

    • initVirtualScroll():首次加載或數據更新時,通過查詢 DOM 獲取表格容器(.el-scrollbar__wrap)的高度,根據當前滾動條位置計算出起始行和可見行數,并調用 setIndex() 更新數據區間。
    • 這確保了頁面一加載時,就只顯示當前視口內的數據,而不會一次性加載所有數據。
  • 滾動事件處理

    • handleScroll({ scrollTop }):每次用戶滾動時,實時根據新的 scrollTop 值重新計算可見區域,并更新 startIndexendIndex
    • 這樣,無論用戶如何快速滾動,頁面始終只渲染當前視口內的數據,保證流暢的滾動體驗。
  • 更新顯示數據區間

    • setIndex(start, end):確保更新后的 startIndex 不低于 0,endIndex 不超過數據總量。
    • 這一步防止了由于計算誤差導致索引越界的情況,保證數據截取始終正確。
  • 防抖優化

    • useEventListener(window, 'resize', useDebounceFn(initVirtualScroll, 100)):在窗口大小變化時,防止因頻繁觸發初始化函數而帶來的性能損耗。
    • 防抖函數確保只有在調整停止一段時間后才執行初始化,相當于給“表格魔術師”一點緩沖時間,避免過度“表演”。

4. 總結

本文深入解析了如何基于 Element Plus 的 <el-table> 組件,通過二次封裝實現虛擬滾動表格。重點在于核心邏輯 useTakeVirtualScroll.ts

  • 利用 Vue 的響應式和 computed 屬性,僅渲染用戶當前視口內的數據。
  • 通過監聽數據變化與滾動事件,動態計算并更新顯示區間,確保頁面渲染始終高效流暢。
  • 防抖優化進一步保障了在窗口調整等情況下的穩定性。

當然,如果你不想自己重造輪子,Element Plus 已經為大家準備好了封裝完善的虛擬表格組件。無論選擇“自己動手”還是“直接用貨”,關鍵在于理解虛擬滾動的原理,從而選出最適合你項目的方案。

希望這篇文章既能幫你學會如何實現高性能的虛擬表格,又能在你選擇方案時提供足夠的參考。如果你有任何疑問或優化建議,歡迎留言交流,讓我們一起玩轉大數據渲染的世界!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70318.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70318.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70318.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

QT 讀寫鎖

一、概述 1、讀寫鎖是一種線程同步機制&#xff0c;用于解決多線程環境下的讀寫競爭問題。 2、讀寫鎖允許多個線程同時獲取讀鎖&#xff08;共享訪問&#xff09;&#xff0c;但只允許一個線程獲取寫鎖&#xff08;獨占訪問&#xff09;。 3、這種機制可以提高并發性能&…

2025 vue3面試題匯總,通俗易懂

一、基礎概念與核心特性 1. Vue3 相比 Vue2 的改進&#xff08;通俗版&#xff09; 問題&#xff1a;Vue3 比 Vue2 好在哪&#xff1f; 答案&#xff1a; 更快&#xff1a; Proxy 代理&#xff1a;Vue2 的響應式像“逐個監聽保險箱”&#xff08;每個屬性單獨監聽&#xff0…

第5章:在LangChain中如何使用AI Services

這篇文章詳細介紹了 LangChain4j 中的 AI Services 概念&#xff0c;展示了如何通過高層次的抽象來簡化與大語言模型&#xff08;LLM&#xff09;的交互。AI Services 的核心思想是隱藏底層復雜性&#xff0c;讓開發者專注于業務邏輯&#xff0c;同時支持聊天記憶、工具調用和 …

二叉樹(數據結構)

二叉樹 二叉樹也是用過遞歸定義的結構 先序遍歷又稱前序遍歷 ?? ?? 按照先序遍歷的方法去手算處理這個二叉樹 ?? 先A B C 再 A B D E C&#xff08;也就是把B換成BDE再放進去&#xff09; 再 A B D E C F 看這個插入的方法要掌握像二叉樹這樣向一個…

機器學習筆記——常用損失函數

大家好&#xff0c;這里是好評筆記&#xff0c;公主號&#xff1a;Goodnote&#xff0c;專欄文章私信限時Free。本筆記介紹機器學習中常見的損失函數和代價函數&#xff0c;各函數的使用場景。 熱門專欄 機器學習 機器學習筆記合集 深度學習 深度學習筆記合集 文章目錄 熱門…

Wireshark使用介紹

文章目錄 Wireshark介紹Wireshark使用工作模式介紹1. 混雜模式&#xff08;Promiscuous Mode&#xff09;2. 普通模式&#xff08;Normal Mode&#xff09;3. 監視模式&#xff08;Monitor Mode&#xff09; 界面分區捕獲過濾器語法基本語法邏輯運算符高級語法使用示例捕獲過濾…

#滲透測試#批量漏洞挖掘#暢捷通T+SQL注入漏洞

免責聲明 本教程僅為合法的教學目的而準備,嚴禁用于任何形式的違法犯罪活動及其他商業行為,在使用本教程前,您應確保該行為符合當地的法律法規,繼續閱讀即表示您需自行承擔所有操作的后果,如有異議,請立即停止本文章讀。 目錄 一、漏洞全景解析 1. 高危漏洞案例庫 2.…

【小游戲】C++控制臺版本俄羅斯輪盤賭

制作團隊&#xff1a;洛谷813622&#xff08;Igallta&#xff09; 989571&#xff08;_ayaka_&#xff09; Mod&#xff1a;_ayaka_ 雙人模式&#xff1a;Igallta 公告&#xff1a; 原先的9.8改名為 Alpha 1.0&#xff0c;以后每次更新都增加 0.1。 Alpha 1.11 改為 Beta 1…

nvm安裝、管理node多版本以及配置環境變量【保姆級教程】

引言 不同的項目運行時可能需要不同的node版本才可以運行&#xff0c;由于來回進行卸載不同版本的node比較麻煩&#xff1b;所以需要使用node工程多版本管理。 本人在配置時&#xff0c;通過網絡搜索教程&#xff0c;由于文章時間過老&#xff0c;或者文章的互相拷貝導致配置時…

框架--Mybatis3

一.特殊符號處理 < < > > " &quot; &apos; & &amp; 除了可以使用上述轉義字符外&#xff0c;還可以使<![CDATA[ ]]>用來包裹特殊字符。 二.mybatis 一級緩存二級緩存 1.為什么緩存 緩存&#xff1a;數據緩存&#xf…

純新手教程:用llama.cpp本地部署DeepSeek蒸餾模型

0. 前言 llama.cpp是一個基于純C/C實現的高性能大語言模型推理引擎&#xff0c;專為優化本地及云端部署而設計。其核心目標在于通過底層硬件加速和量化技術&#xff0c;實現在多樣化硬件平臺上的高效推理&#xff0c;同時保持低資源占用與易用性。 最近DeepSeek太火了&#x…

Netty入門詳解

引言 Netty 是一個基于 Java 的高性能、異步事件驅動的網絡應用框架&#xff0c;用于快速開發可維護的高性能網絡服務器和客戶端。它提供了一組豐富的 API&#xff0c;使得開發人員能夠輕松地處理各種網絡協議&#xff0c;如 TCP、UDP 等&#xff0c;并且支持多種編解碼方式&a…

物聯網簡介集合

物聯網&#xff08;IoT&#xff09;指的是物理設備&#xff08;如電器和車輛&#xff09;之間的互聯互通。這些設備嵌入了軟件、傳感器和連接功能&#xff0c;使其能夠相互連接并交換數據。這項技術實現了從龐大的設備網絡中收集和共享數據&#xff0c;為打造更高效、自動化的系…

【分布式理論11】分布式協同之分布式事務(一個應用操作多個資源):從剛性事務到柔性事務的演進

文章目錄 一. 什么是分布式事務&#xff1f;二. 分布式事務的挑戰三. 事務的ACID特性四. CAP理論與BASE理論1. CAP理論1.1. 三大特性1.2. 三者不能兼得 2. BASE理論 五. 分布式事務解決方案1. 兩階段提交&#xff08;2PC&#xff09;2. TCC&#xff08;Try-Confirm-Cancel&…

【Quest開發】全身跟蹤

軟件&#xff1a;Unity 2022.3.51f1c1、vscode、Meta XR All in One SDK V72 硬件&#xff1a;Meta Quest3 最終效果&#xff1a;能像meta的操作室沉浸場景一樣根據頭盔移動來推斷用戶姿勢&#xff0c;實現走路、蹲下、手勢匹配等功能 需要借助UnityMovement這個包 GitHub …

AI全棧開發_人工智能AI大模型 Prompt提示詞工程詳解(全方位介紹及運用)

AI引領的第四次工業革命正席卷而來&#xff0c;如何精準把握這一歷史性的機遇&#xff0c;將成為我們這一代人不容忽視且需深入思考與積極行動的重要課題。未來幾年AI將會像計算機一樣快速普及&#xff0c;面對這一歷史性的第一波紅利&#xff0c;你是否已準備好把握機遇&#…

小米平板怎么和電腦共享屏幕

最近嘗試使用小米平板和電腦屏幕分屏互聯 發現是需要做特殊處理的&#xff0c;需要下載一款電腦安裝包&#xff1a;小米妙享 關于這個安裝包&#xff0c;想吐槽的是&#xff1a; 沒有找到官網渠道&#xff0c;是通過其他網絡方式查到下載的 不附錄鏈接&#xff0c;原因是因為地…

java | MyBatis-plus映射和golang映射對比

文章目錄 Java實體類和數據庫的映射1.默認駝峰命名規則2.自定義字段映射3.關閉駝峰命名規則4.JSON序列化映射 Golang1. 結構體與表的映射2. 字段與列的映射3. 關聯關系映射4. 其他映射相關標簽 這篇也是做數據庫映射方面的對比&#xff1a; Java 實體類和數據庫的映射 1.默認…

訊方·智匯云校華為官方授權培訓機構

1.官方授權 訊方智匯云校是華為領先級授權培訓機構&#xff08;華為授權培訓合作伙伴&#xff08;HALP&#xff09;體系&#xff0c;分為認證、優選、領先三個等級&#xff0c;領先級是HALP最高級&#xff09;&#xff0c;代表著華為對培訓合作伙伴在專業能力、師資隊伍、合作…

避免踩雷!CUDA與Anaconda兼容性配置完全手冊

CUDA與Anaconda深度學習環境配置指南 目錄 核心概念解析安裝場景分析版本沖突處理最佳實踐指南常見問題解答 核心概念解析 1. 組件對比表 組件作用域包含內容查看方式NVIDIA驅動系統級GPU底層通信支持nvidia-smiCUDA Toolkit系統級完整開發工具鏈(nvcc等)nvcc --versioncon…