作者:TalkingData 李志剛
本文由TalkingData原創(chuàng)悉盆,轉(zhuǎn)載請獲取授權(quán)鸟辅。
李志剛:近幾個月在開發(fā)一個基于Vue的數(shù)據(jù)可視化分析輔助應用———DMap(諦聽)倍奢,一套為數(shù)據(jù)分析師和數(shù)據(jù)科學家提供的基于位置大數(shù)據(jù)分析的工具熟嫩,旨在提高數(shù)據(jù)分析效率,降低獲取多數(shù)據(jù)并行分析成本机错,簡化大屏和數(shù)據(jù)報告開發(fā)制作流程爬范。其UI組件使用的是iView,地圖可視化庫使用的是inMap弱匪,服務端使用Node.js搭建青瀑。
DMap的核心就是服務大數(shù)據(jù)分析,所以當面對幾萬幾十萬甚至百萬級別的數(shù)據(jù)時痢法,性能優(yōu)化是一個具有挑戰(zhàn)性的問題狱窘。今天我就拿項目中一個表格渲染的優(yōu)化為例來展開介紹。
在前端開發(fā)中财搁,用表格來展示數(shù)據(jù)是再平常不過的了蘸炸,當數(shù)據(jù)量較多時,我們通常的做法是使用分頁尖奔,如果數(shù)據(jù)量不算太多只有兩三頁搭儒,我們大可以把全量數(shù)據(jù)獲取下來,在前端做簡單的分頁展示提茁。當數(shù)據(jù)量再上一個等級時淹禾,我們就需要根據(jù)頁數(shù)向服務端請求這一頁需要的數(shù)據(jù)。但是DMap作為助力大數(shù)據(jù)可視化的分析工具茴扁,我們需要將全量的數(shù)據(jù)在前端做展示铃岔,而為了提升用戶體驗,我們在表格的展示上決定不做分頁峭火,也不做懶加載毁习,而是像Excel那樣可以無縫隙的滾動。
在Web中卖丸,長列表渲染的性能問題已經(jīng)有一些成熟的方案纺且,表格和長列表相似,當渲染的行數(shù)達到一定量時稍浆,滾動就會變得卡頓载碌,所以我們使用了虛擬渲染的方案,就是只渲染用戶所能看到的區(qū)域的一小部分數(shù)據(jù)衅枫,然后通過滾動來計算顯示的數(shù)據(jù)嫁艇,和上下占位元素的高度。
通過這個圖可以對原理有個大概的了解为鳄,接下來說下計算上的細節(jié)裳仆。
首先我們需要監(jiān)聽表格外層容器(也就是顯示滾動條的元素)的scroll事件,在scroll事件綁定的方法中我們只做一件事孤钦,那就是獲取外層容器當前滾動了的高度scrollTop的值歧斟。我們的所有計算,包括三個表格位置的替換偏形、表格數(shù)據(jù)的選取静袖、上下占位元素的高度的計算都與scrollTop相關(guān)。
下面是scroll事件的綁定的方法:
handleScroll (e) {
constele = e.srcElement || e.target;
const{ scrollTop, scrollLeft } = ele;
this.scrollLeft = scrollLeft;
this.scrollTop = scrollTop;
}
我們只需要在這里把scrollTop和scrollLeft的值賦給vue實例對應的值俊扭,然后我們用watch監(jiān)聽scrollTop的改變队橙,如果更新了,就來計算當前處于可視區(qū)域的表格索引號currentIndex:
(注:左右滑動即可查看完整代碼萨惑,下同)
this.currentIndex = parseInt((top % (this.moduleHeight *3)) /this.moduleHeight);
這的top就是更新后的this.scrollTop的值捐康,moduleHeight是單個表格的高度,我們稱它為一個模塊庸蔼。
拿到currentIndex的值后解总,我們就可以計算三個表格的顯示位置,和每個表格中填充的數(shù)據(jù)姐仅。三個表格我們是通過render函數(shù)渲染的花枫,我們根據(jù)currentIndex的值來返回不同順序的render函數(shù):
getTables (h) {
let table1 =this.getItemTable(h,this.table1Data,1);
let table2 =this.getItemTable(h,this.table2Data,2);
let table3 =this.getItemTable(h,this.table3Data,3);
if(this.currentIndex ===0)return[table1, table2, table3];
elseif(this.currentIndex ===1)return[table2, table3, table1];
elsereturn[table3, table1, table2];
}
數(shù)組中表格順序不同,反應在頁面上的效果就是不同的先后順序掏膏。最后我們通過這個方法得到完整的render:
renderTable (h) {
returnh('div', {
style:this.tableWidthStyles
},this.getTables(h));
}
然后使用封裝的無狀態(tài)的組件劳翰,來渲染我們得到的表格render。
renderDom組件的實現(xiàn)如下:
exportdefault{
name:'RenderCell',
functional:true,
props: {
render:Function,
backValue: [Number,Object]
},
render:(h, ctx) =>{
returnctx.props.render(h, ctx.props.backValue, ctx.parent);
}
};
接下來我們講下三個表格中填充的數(shù)據(jù)的計算馒疹。
我們按照三個模塊都在可視區(qū)域經(jīng)過一次算是一輪佳簸,通過scrollTop來和currentIndex來計算每個模塊當前是在第幾輪展示,但因為我們是從第二個表格才開始做這個邏輯的處理(為了輪播效果更平滑)颖变,所以要先判斷當前滾動的高度是否大于一個模塊的高度生均,如果大于才做如下計算:
switch (this.currentIndex) {
case0: t0 = parseInt(scrollTop / (this.moduleHeight *3)); t1 = t2 = t0;break;
case1: t1 = parseInt((scrollTop -this.moduleHeight) / (this.moduleHeight *3)); t0 = t1 +1; t2 = t1;break;
case2: t2 = parseInt((scrollTop -this.moduleHeight *2) / (this.moduleHeight *3)); t0 = t1 = t2 +1;
}
計算出每個模塊在第幾輪展示后,就可以來取對應的表格數(shù)據(jù)了:
const count1 =this.times0 *this.itemNum *3;
this.table1Data =this.insideTableData.slice(count1, count1 +this.itemNum);
const count2 =this.times1 *this.itemNum *3;
this.table2Data =this.insideTableData.slice(count2 +this.itemNum, count2 +this.itemNum *2);
const count3 =this.times2 *this.itemNum *3;
this.table3Data =this.insideTableData.slice(count3 +this.itemNum *2, count3 +this.itemNum *3);
到這里虛擬渲染的重要內(nèi)容都介紹完了悼做。表格開發(fā)完成后疯特,在項目中實際使用時,當加載二十多萬條數(shù)據(jù)來測試時肛走,整個頁面卡的讓人無法忍受漓雅,數(shù)據(jù)量越大頁面卡頓越嚴重。我們的表格是沒有問題的朽色,問題出在Vue幫了我們“倒忙”邻吞。
在Vue實例中添加的對象,Vue會先遍歷一遍對象的所有屬性葫男,用——
Object.defineProperty()為每個對象創(chuàng)建對應的getter和setter抱冷。
而在項目中,我們的insideTableData只是一個數(shù)據(jù)集對象中的一個屬性梢褐,這個對象還包括很多與這一個數(shù)據(jù)集相關(guān)的信息旺遮,我們在使用this.insideTableData.slice獲取數(shù)據(jù)的時候會觸發(fā)this.insideTableData對應的getter赵讯,從而執(zhí)行一些其他邏輯,而我們的滾動又會頻繁的(僅當currentIndex變化的時候)需要重新填充表格數(shù)據(jù)耿眉,所以這會造成卡頓边翼。
解決這個問題的辦法就是阻止Vue給我們的數(shù)據(jù)集對象設置對應的setter和getter,
我了解的有兩種方法鸣剪,一是文檔中提到的:
我們使用的時候就需要通過——
this.$data._dataSet.insideTableData(這里的_dataSet就是一個數(shù)據(jù)集對象)來獲取组底。
另一種方法,就是使用ES5的Object.preventExtensions在將數(shù)據(jù)集對象交給Vue實例代理前將對象密封筐骇,這樣數(shù)據(jù)集對象就變成了不可拓展的了债鸡,Vue就不會再添加新的屬性了,也就無法設置setter和getter了铛纬。
做了這個處理后渲染幾十萬數(shù)據(jù)跟玩兒似的流暢厌均。但是阻止Vue設置getter和setter也造成了一些問題,比如原來表格組件中的一些依賴于表格數(shù)據(jù)的計算屬性饺鹃,現(xiàn)在無法在表格數(shù)據(jù)變化時重新計算莫秆,當然了,影響不大悔详,就一個表格行數(shù)的計算镊屎,所以改成了手動設置這個值。
到這里要講的差不多了茄螃,這只是項目中的一點優(yōu)化內(nèi)容缝驳,我封裝的vue-bigdata-table(沒辦法,好名字都被注冊了)表格組件不僅僅這點功能归苍,目前還包括拖動修改列寬用狱、固定列不橫向滾動,固定表頭拼弃、內(nèi)置排序夏伊、編輯單元格、粘貼吻氧、篩選溺忧、自定義表頭和單元格等功能。現(xiàn)在也已經(jīng)開源了盯孙,但是還有很多功能還在開發(fā)中鲁森。