探索Table組件虛擬化

豆皮粉兒們,又見面了,今天這一期,由字節跳動數據平臺的躬馮,帶你們深刻探索Table組件虛擬化原理及其方案。css

0319caf2-aa17-4e88-bfa4-10889cf6f530.gif

本文做者:躬馮前端

前言

列表及表格的虛擬優化不是個新鮮的課題,近期,團隊發現:業界對於table虛擬化居然沒有一個相對一勞永逸的解決方案。這是爲何?又該如何解決?在本文中,咱們會按部就班的介紹在React+AntDesign技術棧下,團隊內部對Table組件虛擬化的不一樣實踐思路,分析可能遇到的難點問題。react

虛擬化問題闡述

在列表頁、瀑布流、Select組件中,咱們都有可能遇到渲染大數量級列表的場景。在過去,咱們總結了一套卓有成效的辦法: 對於列表,咱們經過計算,保證在滾動視窗時,每次只渲染部分元素。這樣既減小了首屏壓力,也保證長時間加載也不會有更多的性能負擔,能夠知足上述大部分場景的高性能優化需求。 具體作法上,咱們利用已知的固定行高和滾動偏移量,計算出滾動到的表格行索引,只渲染出有限視窗內所須要的元素,,並對列表進行相應設置,簡述過程以下:git

  • 計算當前可見區域起始數據的 startIndex
  • 計算當前可見區域結束數據的 endIndex
  • 計算當前可見區域的數據,並渲染到頁面中
  • 計算 startIndex 對應的數據在整個列表中的偏移位置 startOffset,並設置到列表上
  • 計算 endIndex 對應的數據相對於可滾動區域最底部的偏移位置 endOffset,並設置到列表上

這一方案是普遍被大多數開發人員探討的。我推薦參考淺說虛擬列表的實現原理,他對問題的思路,實現,均有比較詳盡的描述。 現在,咱們的場景來到了AntDesign的Table組件。咱們面對十分相似的問題: 業務當中,Table涉及1000+行&100+列數量級別的渲染時,因爲單元格內也具備必定的複雜邏輯,所以頁面渲染時長每每須要卡頓5000ms先後的時間。這顯然是不可以接受的。 實際上,從長列表到Table組件,咱們的列表無非是從一維軸線上升到了二維平面。所以,所謂表格虛擬化,無非就是但願表格能夠實現: 在兼容Table現有功能的狀況下,實現表格只渲染視窗平面內容,對於視窗外的行、列予以隱藏。github

整理現狀

AntDesign

咱們內部的React + umi + AntDesign技術棧是目前前端界較爲常見的一種架構基礎。AntD做爲業界react經常使用組件庫,它提供的Table組件,可以方便的幫咱們解決諸多常見需求,包括並不只限於:行選擇、行展開、行篩選、數據分頁、列固定... 目前AntDesign有AntD@3.26AntD@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,
    }}
  />
  ...
複製代碼

ReactWindow

react-window是一個廣受歡迎的用於解決表格虛擬化問題的開源代碼庫,它產出了不一樣特性的虛擬化組件,以便用戶聚焦不一樣虛擬場景的問題解決。api

react-window的原理並不複雜,主要就是本文在虛擬化問題闡述中對於虛擬化要求內容的實現。主要是經過監控onScroll,動態調整表格橫軸偏移量,節選適當數量的部分數據,進行可視區內容渲染。性能優化

他的前身是react-virtualized。通過了重構和升級,做者對table和list兩種不一樣的場景作了更好的抽象,經過重用共通部分的邏輯,實現了更好的性能,代碼打包大小也減小到了原先的20%。markdown

接入時遇到的問題

看上去,AntD已經給出了一套虛擬化方案。但稍稍深刻調查不難發現,在Github的Issue中,對於官方推薦方案,用戶反饋則不甚理想。 好比:antd

  • 在問題#21022中提出,使用AntD3後,展開、多選等功能點缺失。
  • 在問題#20339中提出,使用AntD4後,大多數表格功能點都存在缺失。

針對上述問題,官方均回覆以用戶自行解決,所以:

按照AntD文檔使用虛擬化進行配置,雖然能夠必定程度實現虛擬化,但會引發多處表格功能缺失。

這是目前咱們主要亟待解決的問題。

rc-table

因爲官方沒有給出很好的虛擬化方案,咱們只好深刻了解AntD內部的架構,尋找問題的切入點。 經閱讀代碼能夠了解,在4.11的AntD/Table代碼架構簡述以下:

  • 在AntDesign/Table中:

    • 初始化表格大小和行列內容
    • 初步整理事件和數據
    • 排序、分頁、過濾等功能對data和column的計算邏輯
    • 調用rc-table依賴
  • 在rc-table中:

    • 註冊各種行列單元格事件
    • 完成各類樣式需求,如列固定,行展開
    • 完成渲染

至此,咱們能夠了解到,AntD架構自己其實已經對錶格邏輯進行了必定程度的劃分,與data數據的順序及內容無關的邏輯,已經被單獨抽象到了rc-table這個庫中。大體上咱們能夠理解爲下圖:

咱們也必需要想清楚:

  • 這三層邏輯,咱們分別對他們作保留,改造,仍是替換?
  • 如保留,如何解決引入虛擬化邏輯後,虛擬化邏輯對現有框架形成的影響?
  • 如不保留,新的框架如何選擇?

組內方案陳列

項目組內對於table的虛擬化工做產生了多種不一樣思路:

方案1:基於rc-table不依賴react-window和AntD實現虛擬化

  • 實現思路:

在不一樣業務中,咱們須要的表格組件功能點並不一致,極端場景下,咱們可能只須要使用少許AntD的Feature,且定製化較高。所以咱們能夠考慮放棄使用AntD,直接使用rc-table並作必定改造。

  • 實現方式:

    • fork一份穩定版本rc-table放入代碼庫內
    • 按照虛擬化原理對rc-table完成必要改造
    • 本身實現排序、選擇、列固定等上層功能點

  • 方案優劣:

    • 優勢:

      • 再也不須要本身實現基礎的表格呈現,這部分有rc-table完成,咱們只須要針對虛擬化對滾動事件作少許改造。
      • 外部功能feature方便自定製。
    • 缺點:

      • 丟失了AntD的功能基礎。

方案2:AntD中截取顯示數據,手動銷燬表格外dom,手動建立佔位符。

  • 實現思路:

當業務重度依賴AntD的各個功能,不能直接移除AntD。咱們只能保留總體框架,找到切入點作部分改造。因此改造antD內表格scroll事件,使用新的onScroll邏輯:

  • 斷定dom行位置,計算index
  • setState動做完成後,手動銷燬超出視窗的dom內容,同時建立等高空白區域,以維護滾動條位置。
  • 對data數據內容進行裁切並更新,保證視窗內數據的正確性。

該邏輯下只修改了AntD傳入的data內容,其他操做均基於jsdom手動完成,影響面小,完成度高。但須要仔細測試對各個表格功能點的影響。

  • 方案優劣:

    • 優勢:

      • 向前兼容Antd Table的配置參數,只須要新增少數props,改形成本極低
      • 直接操控dom,開發工做除了數據截取,其他大部分不依賴AntD/rcTable代碼,性能有保障。
    • 缺點:

      • 須要當心處理改方案對列固定、行展開等特性的影響。
      • 因爲沒法提早獲取行高,暫不支持直接定位,等價方案是須要先搜索到對應的數據,而後將結果裝入InfinityTable

方案3:從新實現表格

  • 實現思路

當項目定製化程度較高時,考慮直接放棄antd。方案3保留了AntD的props定義,以保證從AntD遷移時的便利性,隨後利用react-table完成大多數功能,虛擬化部分藉助了react-window,底層開發了全新的具備虛擬化功能的基礎表格。

  • 框架選擇理由

該方案中引入了github上煊赫一時的開源react hook框架react-table,這是個數據邏輯hook框架,由於排序、選擇、等數據邏輯是具備類似性的,因此不必重寫,它幫助節省了數據處理的成本,同時也沒有干涉UI及虛擬化工做。

  • 方案優劣:

    • 優勢:

      • 深刻改造,重構程度高,方便後續作任何擴展,方便性能優化
      • 利用開源產品實現原AntD的開發內容,節省必定成本
    • 缺點:

      • 表格基礎實現的成本高,基礎table須要結合react-table的api從新實現,遇到各類坑都須要本身踩

改造重點問題覆盤

空白閃爍

  • 問題陳述:

在react-window的README中,能夠看到對此問題的描述。當應用虛擬化後,過快的scroll動做會致使佔位符還沒有更新,只能看到空白內容,須要等少許時間後才加載。當連續快速滾動表格時,呈現不斷閃爍空白內容的狀態。

  • 問題解決:

    • 目前沒有較好解決辦法,增長預載區大小,且優化單元格渲染內容,能夠減緩問題嚴重性。
    • 有同窗提出經過監控scroll時候的speed動態調整預載區大小,不失爲一個沒實踐的思路。

單元格自適應換行

  • 問題陳述:

當單元格文本內容較長時,但願高度能夠自適應。此需求乍一看彷佛能夠利用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以及其數據截取。

  • 問題解決

    • 可使用一個state同步多個table之間的scrollTop,但這種實現有可能由於性能,產生表格渲染的前後,進而產生三張表對不齊的問題。(AntD的Header與body對齊自己也有此問題)
    • AntD不區分三個table,而是在一個table內利用原生tr/td,以及css的sticky特性,部分迴避了該問題,但沒有解決的很好,fixedcolumn存在的場景下,header和body的同步依舊存有相似問題。
    • 能夠將三個表格改成一個表格,而後利用性能最好的3Dtransform來解決此問題。下方推薦的開源產品rsuite-table比較好的經過此方案解決了問題。

列虛擬化

  • 問題描述:

    • 因爲目前方案專一於行數據量大的場景,所以當遇到表格列數據量大的場景,依舊存在性能問題。
    • 若是咱們引入react-window的Grid組件進行列虛擬化,會須要兼容子列,列固定等功能,實際實現比想象中複雜。
  • 問題解決:

    • 過多的列從產品設計來講是不合理的。建議從產品層面改善數據展現問題。
    • 目前暫不支持列虛擬化。

開源表格組件推薦

因爲內部資源不能外部分享,因此沒有辦法把上述方案的產品對外放出。若是您但願使用帶有虛擬化功能的表格,但並不但願作任何開發工做,這裏推薦您幾款比較不錯的開源產品

  • rsuite-table

    • rsuite.github.io/rsuite-tabl…
    • 虛擬化及表格性能優秀,大多數功能點完備
    • 表頭功能薄弱,沒有列篩選,沒有拖拽改變列寬
  • react-base-table

總結

Table組件虛擬化從原理上來講,是比較單純的。即使不借助react-window,咱們也看到了同窗們每一個人均可以用本身的思路實現類似的功能需求。

之因此產生方案叢生沒法統一的現狀,主要仍是因爲不一樣項目對錶格庫的要求不一樣:有的項目須要低成本,有的項目要求兼容各類不一樣feature,有的項目有着繁重的歷史包袱。

而一個新的、面面俱到的Table庫,又會有替換成本、穩定性風險等問題。所以,因地制宜地對本身項目進行改造,實現一套適合本身項目的虛擬化方案反而是最快速的,最被你們接受的。

所以,本文從三個不一樣場景出發,總結了視野可及範圍內,你們解決該問題的不一樣思路。對於但願開箱即用的同窗,也給出了幾個橫向比較下來值得推薦的開源產品。

所謂授人以魚不如授人以漁,但願文中探討的內容,或多或少在提高表格性能及表格虛擬化方向,能給與讀者一些啓發,爲字節系產品的用戶帶來更快更好的體驗。


數據平臺前端團隊,在公司內負責風神、TEA、Libra、Dorado等大數據相關產品的研發。咱們在前端技術上保持着很是強的熱情,除了數據產品相關的研發外,在數據可視化、海量數據處理優化、web excel、WebIDE、私有化部署、工程工具都方面都有不少的探索和積累,有興趣能夠與咱們聯繫~

相關文章
相關標籤/搜索