antd?3.x?Table組件如何快速實現(xiàn)虛擬列表詳析
1. 前言
隨著互聯(lián)網(wǎng)的發(fā)展,web展示的內(nèi)容越來越豐富,也越來越無窮。我們在實際開發(fā)中難免會遇到長列表數(shù)據(jù)渲染,而又不適合分頁的業(yè)務場景,如果瀏覽器直接渲染海量數(shù)據(jù),會造成頁面卡死,嚴重時導致瀏覽器資源耗盡,直接崩潰掉。這種情況用戶與產(chǎn)品是無法接受的,瀏覽器性能與業(yè)務需求產(chǎn)生了對立,因此虛擬列表技術(shù)被提出,為這種尷尬的場面提供了一線生機。
2. 虛擬列表
虛擬列表其實是按需顯示的一種實現(xiàn),即只對可見區(qū)域進行渲染,對非可見區(qū)域中的數(shù)據(jù)不渲染或部分渲染的技術(shù),從而達到極高的渲染性能。假設有10萬條記錄需要同時渲染,我們屏幕的可見區(qū)域的高度為500px,而列表項的高度為50px,則此時我們在屏幕中最多只能看到10個列表項,那么在渲染的時候,我們只需加載可視區(qū)的那10條即可,觸發(fā)頁面滾動時,實時替換當前應該展示在頁面中的10條數(shù)據(jù)。
它的名詞解釋由一張圖來詮釋,如下:
觸發(fā)滾動后,可視區(qū)域內(nèi)的數(shù)據(jù)變化:
- 首先,定義一個
visibleHeight
變量來保存我們可見區(qū)域的高度,作為內(nèi)容容器,并設置為500px,visibleHeight = 500
。 - 假設每列高度
itemHeight
固定,則可見區(qū)域內(nèi)的數(shù)據(jù)條數(shù)visibleCount = Math.ceil(visibleHeight / itemHeight)
。 - 由于只渲染可視區(qū)域內(nèi)的數(shù)據(jù),所以我們需要另一個占位容器,使父盒子出現(xiàn)滾動條,占位容器高度
placeholderHeight = totalCount * itemHeight
。占位容器替代了內(nèi)容容器,撐出了滾動條,內(nèi)容盒子則采用絕對定位脫離文檔流。 - 每當觸發(fā)滾動時,獲取滾動條的滾動距離,計算出應該總共滾動了的數(shù)據(jù)條數(shù),從而設置數(shù)據(jù)的開始索引
startIdx = Math.floor(scrollTop / itemHeight)
,而結(jié)束索引endIdx = startIdx + visibleCount
。 - 如果是原生開發(fā),則根據(jù)開始、結(jié)束索引去操作dom,替換dom。如果是vue或react都是數(shù)據(jù)驅(qū)動,則更新要渲染的list數(shù)據(jù)即可,
renderList = sourceList.slice(startIdx, endIdx)
- 最后一點,我們的可視數(shù)據(jù)列表需要根據(jù)每次滾動的距離相應地調(diào)整內(nèi)容容器的位置,以保證在父盒子的滾動下,內(nèi)容容器始終在可視區(qū)域中,
offset = startIdx * itemCount
,offset
則為內(nèi)容容器相對于占位容器的偏移距離。
首先,準備dom結(jié)構(gòu):
<div className="wrapper" style={{ position: "relative", overflow: "auto" }}> <div className="placeholder-list" style={{ height: `${visibleHeight}px` }}></div> <div className="render-list" style={{ postion: "absolute", top: 0, left: 0 }}>...</div> </div>
為wrapper
添加滾動事件實現(xiàn)邏輯:
scrollEvent(e){ const startIdx = Math.floor(e.target.scrollTop / itemHeight); const endIdx = startIdx + visibleCount; setList(source.slice(startIdx, endIdx)); // 設置偏移距離,保持數(shù)據(jù)在視圖中 const offset = startIdx * itemHeight; listRef.current.style.top = offset + "px"; }
我們發(fā)現(xiàn),快速滾動時最下方會出現(xiàn)空白的現(xiàn)象,因為此時數(shù)據(jù)還沒渲染成功。為了優(yōu)化此空白,考慮多渲染2條數(shù)據(jù)作為緩沖區(qū)。因此visibelCount=Math.ceil(visibelHeight / itemHeight) + 2
代碼完整示例:
import { useCallback, useEffect, useRef, useState } from "react"; const visibleHeight = 360; const itemHeight = 50; const visibleCount = Math.ceil(visibleHeight / itemHeight) + 2; const totalCount = 100; const source = Array.from(Array(totalCount), (item, index) => index); export default function VirtualList() { const [list, setList] = useState(source); const listRef = useRef(); const scrollEvent = useCallback((e) => { const startIdx = Math.floor(e.target.scrollTop / itemHeight); const endIdx = startIdx + visibleCount; setList(source.slice(startIdx, endIdx)); const offset = startIdx * itemHeight; listRef.current.style.top = offset + "px"; }, []); useEffect(() => { listRef.current = document.querySelector(".list"); }, []); return ( <div style={{ backgroundColor: "#FFF", height: visibleHeight + 'px', textAlign: "center", overflow: "auto", position: "relative", overscrollBehavior: 'contain' }} onScroll={scrollEvent} > <div style={{ height: totalCount * itemHeight + 'px' }}></div> <div className="list" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: visibleHeight + 'px' }} > {list.map((item) => { return ( <div key={item} style={{ height: itemHeight + 'px', borderBottom: "1px solid #eee" }} > {item} </div> ); })} </div> </div> ); }
3. 虛擬table
終于來到了標題的內(nèi)容,如何對antd table3.x進行虛擬表格的封裝。其實和上述的代碼差不多,只不過對于有的新手同學來講,可能有點摸不著入口,所以有了本節(jié)內(nèi)容。
目前在antd4.x版本table已經(jīng)實現(xiàn)了開啟虛擬列表的配置,拿來即用。針對3.x的版本自己實現(xiàn)了一個虛擬table,解決了業(yè)務上長列表渲染卡頓的問題。
注意:Table每項需要定高,因此columns屬性中需要ellipsis:true保證數(shù)據(jù)只展示一行,溢出展示省略號。
根據(jù)上節(jié)內(nèi)容介紹的虛擬列表思路,我們需要準備2個容器,一個內(nèi)容容器,Table
已經(jīng)提供了,另一個占位容器沒有提供,所以需要手動創(chuàng)建一個并放在合適的地方。通過開發(fā)者工具審查元素找到Table
內(nèi)提供的那個內(nèi)容容器.ant-table-body table
,獲取其dom。父容器.ant-table-body
,創(chuàng)建一個占位容器div,追加到父容器內(nèi)。通過元素審查也知道Table tr高度為54px
,即itemHeight=54
知道了類名,就可以獲取到Table
的dom為所欲為了。
useEffect(() => { const parentNode = document.querySelector('.ant-table-body'); const table = document.querySelector('.ant-table-body table'); // 用ref保持table方便在滾動事件中使用table dom tableRef.current = table; // 創(chuàng)建一個占位的div,高度等于所有數(shù)據(jù)高度,用來撐開容器展示滾動條 const placeholderWrapper = document.createElement('div'); placeholderWrapper.style.height = itemHeight * totalCount + 'px' parentNode.appendChild(placeholderWrapper); // 子絕父相口訣,為table設置定位,脫離文檔流,把位置讓給占位盒子 parentNode.style.position = 'relative'; table.style.position = 'absolute'; table.style.top = 0; table.style.left = 0; // 添加滾動事件 parentNode.addEventListener('scroll', scrollEvent) return () => { // 清理占位盒子 parentNode.removeChild(placeholderWrapper); parentNode.removeEventListener('scroll', scrollEvent) } }, [scrollEvent]);
接下來實現(xiàn)滾動事件,和上節(jié)內(nèi)容一致,保存范圍索引到state中:
const scrollEvent = useCallback((e) => { const startIdx = Math.floor(e.target.scrollTop / itemHeight); const endIdx = startIdx + visibleCount; // 保存當前的范圍索引,用來slice源數(shù)據(jù)給展示用 setRange([startIdx, endIdx]); const offset = startIdx * itemHeight; tableRef.current.style.top = offset + "px"; }, []);
根據(jù)范圍索引,截取當前要展示的數(shù)據(jù)項
const [range, setRange] = useState([]); // 這個renderList就是需要給Table組件的 const renderList = useMemo(() => { const [start, end] = range; return dataSource.slice(start, end) }, [range]) return <Table dataSource={renderList} />
全文示例代碼Github地址。
4.總結(jié)
本文只是實現(xiàn)了在固定每項列表高度的情況下的虛擬列表,現(xiàn)實很多情況是不定高的。這個比定高的復雜,不過原理也是一樣的,多了一步需要計算渲染后的實際高度的步驟。后續(xù)會完善不定高的虛擬列表的實現(xiàn)。
本文的內(nèi)容也是我在工作中遇到的情況,應該很多其他小伙伴也會遇到antd 3.x table的虛擬化的問題,希望能給小伙伴們一點思路。因此有了本文,也是自己一次關(guān)于輸入與輸出的記錄與沉淀。
到此這篇關(guān)于antd 3.x Table組件如何快速實現(xiàn)虛擬列表的文章就介紹到這了,更多相關(guān)antd 3.x Table組件虛擬列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入學習TypeScript 、React、 Redux和Ant-Design的最佳實踐
這篇文章主要介紹了深入學習TypeScript 、React、 Redux和Ant-Design的最佳實踐,TypeScript 增加了代碼的可讀性和可維護性,擁有活躍的社區(qū),,需要的朋友可以參考下2019-06-06詳解create-react-app 2.0版本如何啟用裝飾器語法
這篇文章主要介紹了詳解create-react-app 2.0版本如何啟用裝飾器語法,cra2.0時代如何啟用裝飾器語法呢? 我們依舊采用的是react-app-rewired, 通過劫持webpack cofig對象, 達到修改的目的2018-10-10關(guān)于useEffect執(zhí)行兩次的問題及解決
這篇文章主要介紹了關(guān)于useEffect執(zhí)行兩次的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09React組件實例三大核心屬性State props Refs詳解
組件實例的三大核心屬性是:State、Props、Refs。類組件中這三大屬性都存在。函數(shù)式組件中訪問不到 this,也就不存在組件實例這種說法,但由于它的特殊性(函數(shù)可以接收參數(shù)),所以存在Props這種屬性2022-12-12解決React報錯Encountered?two?children?with?the?same?key
這篇文章主要為大家介紹了React報錯Encountered?two?children?with?the?same?key解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12