豆皮粉兒們,又見面了,今天這一期,由字節跳動數據平臺的躬馮,帶你們深刻探索Table組件虛擬化原理及其方案。css
本文做者:躬馮前端
列表及表格的虛擬優化不是個新鮮的課題,近期,團隊發現:業界對於table虛擬化居然沒有一個相對一勞永逸的解決方案。這是爲何?又該如何解決?在本文中,咱們會按部就班的介紹在React+AntDesign技術棧下,團隊內部對Table組件虛擬化的不一樣實踐思路,分析可能遇到的難點問題。react
在列表頁、瀑布流、Select組件中,咱們都有可能遇到渲染大數量級列表的場景。在過去,咱們總結了一套卓有成效的辦法: 對於列表,咱們經過計算,保證在滾動視窗時,每次只渲染部分元素。這樣既減小了首屏壓力,也保證長時間加載也不會有更多的性能負擔,能夠知足上述大部分場景的高性能優化需求。 具體作法上,咱們利用已知的固定行高和滾動偏移量,計算出滾動到的表格行索引,只渲染出有限視窗內所須要的元素,,並對列表進行相應設置,簡述過程以下:git
這一方案是普遍被大多數開發人員探討的。我推薦參考淺說虛擬列表的實現原理,他對問題的思路,實現,均有比較詳盡的描述。 現在,咱們的場景來到了AntDesign的Table組件。咱們面對十分相似的問題: 業務當中,Table涉及1000+行&100+列數量級別的渲染時,因爲單元格內也具備必定的複雜邏輯,所以頁面渲染時長每每須要卡頓5000ms先後的時間。這顯然是不可以接受的。 實際上,從長列表到Table組件,咱們的列表無非是從一維軸線上升到了二維平面。所以,所謂表格虛擬化,無非就是但願表格能夠實現: 在兼容Table現有功能的狀況下,實現表格只渲染視窗平面內容,對於視窗外的行、列予以隱藏。github
咱們內部的React + umi + AntDesign技術棧是目前前端界較爲常見的一種架構基礎。AntD做爲業界react經常使用組件庫,它提供的Table組件,可以方便的幫咱們解決諸多常見需求,包括並不只限於:行選擇、行展開、行篩選、數據分頁、列固定... 目前AntDesign有AntD@3.26和AntD@4.11兩大版本,後者通過必定的重構和優化,是官方推薦的最新內容。AntDesign3文檔中已經刪除了對於虛擬化支持的demo(實際也可使用),而在AntDesign4文檔中,能看到官方推薦用戶以接入react-window的方式解決虛擬化表格問題。 從官方的Demo來看,AntD提供了一個components屬性,經過傳入一個對象,在其body屬性中給到一個ReactWindow提供的虛擬化組件,以實現需求web
...
// VariableSizeGrid is a component provided by react-window
const renderVirtualList = (rawData, { scrollbarSize, ref, onScroll })=>
<VariableSizeGrid
{...props}
/>
...
<Table
{...props}
className="virtual-table"
columns={mergedColumns}
pagination={false}
components={{
// overwrite the body set by AntD
body: renderVirtualList,
}}
/>
...
複製代碼
react-window是一個廣受歡迎的用於解決表格虛擬化問題的開源代碼庫,它產出了不一樣特性的虛擬化組件,以便用戶聚焦不一樣虛擬場景的問題解決。api
react-window的原理並不複雜,主要就是本文在虛擬化問題闡述中對於虛擬化要求內容的實現。主要是經過監控onScroll,動態調整表格橫軸偏移量,節選適當數量的部分數據,進行可視區內容渲染。性能優化
他的前身是react-virtualized。通過了重構和升級,做者對table和list兩種不一樣的場景作了更好的抽象,經過重用共通部分的邏輯,實現了更好的性能,代碼打包大小也減小到了原先的20%。markdown
看上去,AntD已經給出了一套虛擬化方案。但稍稍深刻調查不難發現,在Github的Issue中,對於官方推薦方案,用戶反饋則不甚理想。 好比:antd
針對上述問題,官方均回覆以用戶自行解決,所以:
按照AntD文檔使用虛擬化進行配置,雖然能夠必定程度實現虛擬化,但會引發多處表格功能缺失。
這是目前咱們主要亟待解決的問題。
因爲官方沒有給出很好的虛擬化方案,咱們只好深刻了解AntD內部的架構,尋找問題的切入點。 經閱讀代碼能夠了解,在4.11的AntD/Table代碼架構簡述以下:
在AntDesign/Table中:
在rc-table中:
至此,咱們能夠了解到,AntD架構自己其實已經對錶格邏輯進行了必定程度的劃分,與data數據的順序及內容無關的邏輯,已經被單獨抽象到了rc-table這個庫中。大體上咱們能夠理解爲下圖:
咱們也必需要想清楚:
項目組內對於table的虛擬化工做產生了多種不一樣思路:
在不一樣業務中,咱們須要的表格組件功能點並不一致,極端場景下,咱們可能只須要使用少許AntD的Feature,且定製化較高。所以咱們能夠考慮放棄使用AntD,直接使用rc-table並作必定改造。
實現方式:
方案優劣:
優勢:
缺點:
當業務重度依賴AntD的各個功能,不能直接移除AntD。咱們只能保留總體框架,找到切入點作部分改造。因此改造antD內表格scroll事件,使用新的onScroll邏輯:
該邏輯下只修改了AntD傳入的data內容,其他操做均基於jsdom手動完成,影響面小,完成度高。但須要仔細測試對各個表格功能點的影響。
方案優劣:
優勢:
缺點:
當項目定製化程度較高時,考慮直接放棄antd。方案3保留了AntD的props定義,以保證從AntD遷移時的便利性,隨後利用react-table完成大多數功能,虛擬化部分藉助了react-window,底層開發了全新的具備虛擬化功能的基礎表格。
該方案中引入了github上煊赫一時的開源react hook框架react-table,這是個數據邏輯hook框架,由於排序、選擇、等數據邏輯是具備類似性的,因此不必重寫,它幫助節省了數據處理的成本,同時也沒有干涉UI及虛擬化工做。
方案優劣:
優勢:
缺點:
在react-window的README中,能夠看到對此問題的描述。當應用虛擬化後,過快的scroll動做會致使佔位符還沒有更新,只能看到空白內容,須要等少許時間後才加載。當連續快速滾動表格時,呈現不斷閃爍空白內容的狀態。
問題解決:
當單元格文本內容較長時,但願高度能夠自適應。此需求乍一看彷佛能夠利用react-window的不定高組件解決,但實際上,該組件須要提供一個高度函數:
// Returns the size of a item in the direction being windowed.
// For vertical lists, this is the row height.
// For horizontal lists, this is the column width.
itemSize: (index: number) => number
複製代碼
當文本變化時,咱們也須要render後才能獲取每一行的高度,因此這個api並無想象中美好。該問題仍是必須藉助二次渲染後,經過ref拿dom節點才能解決,但這樣會拖慢性能,所以考量後,對此方案不予支持
在方案3中,因爲使用了div的flex排版而不是原生table,爲了實現列固定時,咱們須要使用三個table來作固定效果,所以滾動時,咱們須要同時改變多個表格的scrollTop以及其數據截取。
問題解決
問題描述:
問題解決:
因爲內部資源不能外部分享,因此沒有辦法把上述方案的產品對外放出。若是您但願使用帶有虛擬化功能的表格,但並不但願作任何開發工做,這裏推薦您幾款比較不錯的開源產品
rsuite-table
react-base-table
Table組件虛擬化從原理上來講,是比較單純的。即使不借助react-window,咱們也看到了同窗們每一個人均可以用本身的思路實現類似的功能需求。
之因此產生方案叢生沒法統一的現狀,主要仍是因爲不一樣項目對錶格庫的要求不一樣:有的項目須要低成本,有的項目要求兼容各類不一樣feature,有的項目有着繁重的歷史包袱。
而一個新的、面面俱到的Table庫,又會有替換成本、穩定性風險等問題。所以,因地制宜地對本身項目進行改造,實現一套適合本身項目的虛擬化方案反而是最快速的,最被你們接受的。
所以,本文從三個不一樣場景出發,總結了視野可及範圍內,你們解決該問題的不一樣思路。對於但願開箱即用的同窗,也給出了幾個橫向比較下來值得推薦的開源產品。
所謂授人以魚不如授人以漁,但願文中探討的內容,或多或少在提高表格性能及表格虛擬化方向,能給與讀者一些啓發,爲字節系產品的用戶帶來更快更好的體驗。
數據平臺前端團隊,在公司內負責風神、TEA、Libra、Dorado等大數據相關產品的研發。咱們在前端技術上保持着很是強的熱情,除了數據產品相關的研發外,在數據可視化、海量數據處理優化、web excel、WebIDE、私有化部署、工程工具都方面都有不少的探索和積累,有興趣能夠與咱們聯繫~