Tree組件實現(xiàn)支持50W數(shù)據(jù)方法剖析
出師未捷身先死
有用戶在 fes-design VIP群 吐槽 Tree
組件在處理一萬條左右數(shù)據(jù)時很卡。但是 fes-design 已重視大數(shù)據(jù)場景,提供基礎的虛擬列表組件,以及選擇器、表格、樹形、級聯(lián)等組件基于虛擬列表處理了大數(shù)據(jù)場景,為啥 Tree
組件還卡呢?
Tree 自身的復雜性
Tree
數(shù)據(jù)結構特性決定 Tree
組件中父子節(jié)點存在關聯(lián),以選中功能為例:
Select:選中只影響自身狀態(tài)。
Tree:當開啟父子關聯(lián)時,選中某個節(jié)點時,其所有子孫節(jié)點全部選中,同時需計算父輩節(jié)點是否為全選中。
虛擬滾動帶來的復雜性
虛擬滾動是指根據(jù)滾動距離計算當前視野范圍需要展示的內容。不管有多少數(shù)據(jù),只渲染視野范圍內的選項,大大減少了 Vue 實例的創(chuàng)建,性能無比優(yōu)越。因為虛擬滾動只接受一維數(shù)組結構,所以Tree
組件在初始化時需要把樹狀結構數(shù)據(jù)按照展示順序拍平為一維數(shù)組。那么展開關閉的功能就變得復雜了!
不考慮虛擬滾動方案時節(jié)點會這么設計:
<div class="node"> <div>{{ node.label }}</div> <div v-show="node.expanded" v-for="child in node.children"> <Node node="child"/> </div> </div>
展開關閉只需要改變 node.expanded
。
考慮虛擬滾動方案時節(jié)點會這么設計:
<div class="node"> <div>{{ node.label }}</div> </div>
計算所有子孫節(jié)點狀態(tài),判斷節(jié)點是否顯示,如果顯示則把當前節(jié)點丟到虛擬滾動的一維數(shù)組中。
查問題
先用chrome的性能測試工具看看問題在哪:
可以找到耗時的代碼語句,下一步干掉他們。
怎么做
緩存數(shù)據(jù)
Tree
組件在初始化時會把樹狀結構數(shù)據(jù)按照展示順序拍平為一維數(shù)組,在這個過程中,記錄每個節(jié)點的父級節(jié)點為indexPath 和所有子孫節(jié)點childrenPath。在后續(xù)邏輯中經常會用到:
// 當選中某個節(jié)點時,只需要處理此節(jié)點相關上下節(jié)點狀態(tài) if (checkingNode) { const { indexPath } = checkingNode; indexPath.slice(0).reverse().forEach(computeIndeterminate); checkingNode.hasChildren && checkingNode.childrenPath.forEach( (key: TreeNodeKey) => { const node = nodeList.get(key); node.isIndeterminate.value = false; }, ); checkingNode = null; }
減少響應式數(shù)據(jù)
在優(yōu)化前所有節(jié)點都會丟到nodeList中:
const nodeList = reactive<TreeNodeList>({}); // 轉換節(jié)點數(shù)據(jù) const copy = transformNode(node, indexPath, level); nodeList[copy.value] = copy;
數(shù)據(jù)量上來后,數(shù)據(jù)響應式處理耗時非常大。所以我們不要把整個對象一股腦弄成響應式的,只把需要的字段設置為響應式的。
Tree
節(jié)點需要緩存的內部狀態(tài)有是否開展、是否全選、是否選中,所以只需要這三個字段為響應式:
const nodeList: Map<TreeNodeKey, InnerTreeOption> = new Map(); f (!nodeList.get(value)) { // Object.assign比解構快很多 copy = Object.assign({}, newItem); copy.isExpanded = ref(false); copy.isIndeterminate = ref(false); copy.isChecked = ref(false); } nodeList.set(copy.value, copy);
用更快的 JS 語法
1、Array.concat 性能比較慢,改為使用賦值
export function concat(arr: any[], arr2: any[]) { const arrLength = arr.length; const arr2Length = arr2.length; arr.length = arrLength + arr2Length; for (let i = 0; i < arr2Length; i++) { arr[arrLength + i] = arr2[i]; } return arr; }
2、Map 的查找性能比 Object 稍好
const nodeList = {} ;
改為使用
const nodeList = new Map();
3、解構語法比較慢,改為使用Object.assign
扣細節(jié)
1、computeCurrentData 是執(zhí)行非常耗時的函數(shù),由于 watch 兩個變量,在初始化時會執(zhí)行兩次,加上debounce只需要執(zhí)行一次。
watch( [currentExpandedKeys, transformData], debounce(() => { if (isSearchingRef.value) return; computeCurrentData(); }, 10), { immediate: true, }, );
2、葉子節(jié)點不需要計算isExpanded
if (node.hasChildren) { node.isExpanded.value = expandedKeys.includes(key); }
3、計算顯示的節(jié)點時,可以先判斷是否由展開或者關閉節(jié)點觸發(fā)的計算,如果是則只需要計算此節(jié)點子孫和父級節(jié)點狀態(tài),而不需要計算全部節(jié)點
const computeCurrentData = ()=> { if(expandingNode) { // 計算此節(jié)點相關節(jié)點 return } // 遍歷所有節(jié)點 }
類似這種細節(jié)非常多,通過性能測試工具和自己經驗能找到很多地方,積少成多,性能能提升不少。
數(shù)據(jù)結構一致性的魅力
以收起節(jié)點為例:
常規(guī)思路是:當點擊收起節(jié)點時,判斷當前所有子孫節(jié)點是否在顯示數(shù)據(jù)數(shù)組中,如果在就刪掉。復雜度是O(n^2)。
但是可以換個思路:由于childrenPath和currentData的順序一致,只需要遍歷一次childrenPath,判斷是是否為當前節(jié)點下一個節(jié)點,如果是,刪掉就好。復雜度是O(n)
const deleteNode = (keys: TreeNodeKey[], index: number) => { let len = 0; keys.forEach((key) => { if (key === currentData.value[index + len]) { len += 1; } }); currentData.value.splice(index, len); }; const index = currentData.value.indexOf(expandingNode.value); deleteNode(expandingNode.childrenPath, index + 1);
在 Tree
的代碼中有很多地方,可以通過特殊的數(shù)據(jù)結構來減少或者避免循環(huán),性能提升非常大!
歡迎來體驗: fes-design
以上就是Tree組件實現(xiàn)支持50W數(shù)據(jù)方法剖析的詳細內容,更多關于Tree組件50W數(shù)據(jù)的資料請關注腳本之家其它相關文章!
相關文章
Java實現(xiàn)TCP/IP協(xié)議的收發(fā)數(shù)據(jù)(服務端)代碼實例
這篇文章主要介紹了Java實現(xiàn)TCP/IP協(xié)議的收發(fā)數(shù)據(jù)(服務端)代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11Spring Boot中@ConditionalOnProperty的使用方法
這篇文章主要給大家介紹了關于Spring Boot中@ConditionalOnProperty的使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用Spring Boot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-12-12Java微信公眾平臺開發(fā)(7) 公眾平臺測試帳號的申請
這篇文章主要為大家詳細介紹了Java微信公眾平臺開發(fā)第七步,微信公眾平臺測試帳號的申請,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04MybatisPlus如何自定義TypeHandler映射JSON類型為List
這篇文章主要介紹了MybatisPlus如何自定義TypeHandler映射JSON類型為List,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Java格式化輸出詳細講解(printf、print、println、format等)
Java的格式化輸出等同于String.Format,與C有很大的相似,下面這篇文章主要給大家介紹了關于Java格式化輸出(printf、print、println、format等)的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-03-03java對象序列化與反序列化的默認格式和json格式使用示例
這篇文章主要介紹了java對象序列化與反序列化的默認格式和json格式使用示例,需要的朋友可以參考下2014-02-02