前端tree組件,10000個樹節點,從12.19s到0.49s

全篇思想:

遞歸的本質是棧的讀取

如下都是基於10000條子節點數據做對比
css

先看效果對比:

遞歸版tree,渲染速度: 12.19s,點擊節點處理速度: 9.52s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s
vue

遞歸組件實現tree:渲染速度 13.75s -1.56s = 12.19s


                                             遞歸組件版tree性能圖-1node

遞歸組件版tree點擊節點性能分析圖:點擊節點處理速度: 9.64s - 0.117s = 9.523s ≈ 9.52s算法

                                             遞歸版tree點擊節點性能圖-2

優化版tree: 渲染速度2.54s - 2.05s = 0.49s數組

                                               優化版tree性能圖-3

優化版tree點擊節點性能分析圖:點擊節點處理速度2.53s - 2.35s = 0.18sbash

                                                優化版tree點擊節點性能圖-4

最終對比是:數據結構

遞歸版tree,渲染速度: 12.19s,點擊節點處理速度: 9.52s
優化版tree,渲染速度: 0.49s,點擊節點處理速度: 0.18s

分析問題

咱們能夠藉助performance分析一下遞歸組件的耗時點所在,上遞歸組件渲染的性能分析:dom

一、script耗時分析:post

                                             遞歸tree script性能分析圖-5

經過圖-1性能瀑布能夠清晰的看到script執行佔了8.9s的時間,經過上圖即圖-5能夠看到script的的調用棧主要集中在建立vue實例時的createChildren上面。
性能

二、render耗時分析:

                                                遞歸tree render性能分析圖-6

經過上圖即圖-6能夠清晰的看到render耗時主要集中在Recalculate Style、Layout上面。咱們知道Recalculate Style、Layout主要是樣式計算,所以查看代碼:

                                                遞歸組件部分代碼圖-7

發如今遞歸的tree-node組件裏面有不少樣式的計算,10000條子節點就須要計算10000次。

三、DOM結構分析:

                                               遞歸組件DOM結構圖-8

由上圖即圖-8咱們能夠看出遞歸組件生成的DOM結構也相應是嵌套的結構,所以DOM不只有縱向的結構也同時有嵌套層次的結構,所以這時控制DOM的數量很難。

由上面的script、render、DOM結構的分析,能夠看出問題癥結所在:建立vue實例過多!

所以咱們若是要優化,那麼只要減小vue實例的建立,那麼問題就獲得瞭解決,所以接下來來看是如何實現的。

實現思想

來看咱們的開篇思想:

遞歸的本質是棧的讀取

在算法中咱們會遇到不少遞歸實現的案例,全部的遞歸均可以轉換成非遞歸實現,其中轉換的本質是:遞歸是解析器(引擎)來幫咱們作了棧的存取,非遞歸是手動建立棧來模擬棧的存取過程

萬物都是相通的,遞歸組件也能夠轉換成扁平數組來實現:

一、更改DOM結構成平級結構,點擊節點以及節點的視覺樣式經過操做總的list數據去實現

二、而後使用虛擬長列表來控制vue子組件實例建立的數量


優化版實現

主要分爲兩部分功能:

一、tree數據和DOM結構的扁平化
二、虛擬長列表控制DOM渲染數量

一、tree數據和DOM結構的扁平化

                                        優化版tree的DOM結構圖-9

由上圖咱們能夠看到通過改造以後的tree的DOM結構,父節點和子節點是平級的,在操做子節點時去操做內存中的listData數據來改變相關聯節點的狀態。

咱們再看下listData數據的結構,:

優化版tree listData數據結構圖-10

上圖即圖-10結合圖-9的DOM結構能夠對整個功能的實現邏輯一目瞭然:

listData中的每一項的style、checked、path等信息來描述節點的樣式位置和狀態,操做一個節點時經過listData更改相關節點的狀態樣式等信息。

所以最終來寫咱們的代碼:

實現.vue代碼圖-11

咱們再看下handleCheckChange的作了什麼:

handleCheckChange處理邏輯圖-12

handleCheckChange 處理了父節點和子節點的check和半選狀態,一切都是操做的listData中的數據。

如今咱們是把全部listData都經過循環渲染出來,其實咱們能夠只渲染可視區的節點數據。

listData數據的扁平化及DOM的扁平化爲接下來咱們接入虛擬長列表的功能提供了可能。

二、虛擬長列表控制DOM渲染數量

實現思路:

根節點DOM分紅兩個子節點:fui-tree__phantom 和 fui-tree__content

兩個子節點都是絕對定位,爲了在滾動時避免數據的更改回頭觸發滾動事件

虛擬列表DOM組織結構圖-13

根節點解兩個子節點css:

.fui-tree {
    height: 400px;
    overflow: auto;
    position: relative;
  }

  .fui-tree__phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
  }

  .fui-tree__content {
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
  }
複製代碼

而後咱們經過滾動條的位置來計算咱們應該要取哪些數據。

主要代碼:

                                  計算可視區數據的起止索引index圖-14

經過startIndex、endIndex能夠取出咱們須要循環的數據列表renderNodes:

computed: {
      renderNodes() {
        if (!this.treeNode) return []
        return this.treeNode.listData.slice(this.positionConfig.startIndex, this.positionConfig.endIndex)
      },
      phantomStyle() {
        return {
          height: this.allListLen * 20 + 'px'
        }
      }
    }
複製代碼

結合圖-11的v-for,這樣咱們在渲染時的dom數量是固定的條數,以下圖:

                                          優化版tree DOM數量是固定的圖-15

虛擬列表的接入可讓即便再多數據量也能渲染固定的DOM數量,這樣就能夠支撐更大數據的渲染和功能。


以上咱們實現了業務需求的大數據渲染,目前測試可支撐到20w條節點,點擊子節點時會有肉眼可見的延遲,主要是圖-12中handleCheckChange的數據查找和處理,這塊還有必定的優化空間:使用字典樹存儲節點相關信息,字典樹和扁平數組listData的每個元素指向同一個內存地址,在handleCheckChange中經過操做字典樹來達到操做listData的元素的效果,經典的空間換時間的案例。

歡迎關注葉雨森的知乎專欄:zhuanlan.zhihu.com/p/55528376

參考:

zhuanlan.zhihu.com/p/34585166

juejin.im/post/5c4154…

相關文章
相關標籤/搜索