DMap(諦聽)——實戰Vue百萬條資料渲染表格元件開發

_Lison發表於2018-04-19

      近幾個月在開發一個基於Vue的資料視覺化分析輔助應用———DMap(諦聽),一套為資料分析師和資料科學家提供的基於位置大資料分析的工具,旨在提高資料分析效率,降低獲取多資料並行分析成本,簡化大屏和資料包告開發製作流程。UI元件使用的是iView,地圖視覺化庫使用的是inMap,服務端使用Node.js搭建。

DMap(諦聽)——實戰Vue百萬條資料渲染表格元件開發

      DMap的核心就是服務大資料分析,所以當面對幾萬幾十萬甚至百萬級別的資料時,效能優化是一個具有挑戰性的問題。今天我就拿專案中一個表格渲染的優化為例來展開介紹。

      在前端開發中,用表格來展示資料是再平常不過的了,當資料量較多時,我們通常的做法是使用分頁,如果資料量不算太多隻有兩三頁,我們大可以把全量資料獲取下來,在前端做簡單的分頁展示。當資料量再上一個等級時,我們就需要根據頁數向服務端請求這一頁需要的資料。但是DMap作為助力大資料視覺化的分析工具,我們需要將全量的資料在前端做展示,而為了提升使用者體驗,我們在表格的展示上決定不做分頁,也不做懶載入,而是像Excel那樣可以無縫隙的滾動。

      在Web中,長列表渲染的效能問題已經有一些成熟的方案,表格和長列表相似,當渲染的行數達到一定量時,滾動就會變得卡頓,所以我們使用了虛擬渲染的方案,就是隻渲染使用者所能看到的區域的一小部分資料,然後通過滾動來計算顯示的資料,和上下佔位元素的高度。

DMap(諦聽)——實戰Vue百萬條資料渲染表格元件開發

      通過這個圖可以對原理有個大概的瞭解,接下來說下計算上的細節。

      首先我們需要監聽表格外層容器(也就是顯示滾動條的元素)的scroll事件,在scroll事件繫結的方法中我們只做一件事,那就是獲取外層容器當前滾動了的高度scrollTop的值。我們的所有計算,包括三個表格位置的替換、表格資料的選取、上下佔位元素的高度的計算都與scrollTop相關。

      下面是scroll事件的繫結的方法:

    handleScroll (e) {
      const ele = e.srcElement || e.target;
      const { scrollTop, scrollLeft } = ele;
      this.scrollLeft = scrollLeft;
      this.scrollTop = scrollTop;
    }
複製程式碼

      我們只需要在這裡把scrollTop和scrollLeft的值賦給vue例項對應的值,然後我們用watch監聽scrollTop的改變,如果更新了,就來計算當前處於可視區域的表格索引號currentIndex:

this.currentIndex = parseInt((top % (this.moduleHeight * 3)) / this.moduleHeight);
複製程式碼

      這的top就是更新後的this.scrollTop的值,moduleHeight是單個表格的高度,我們稱它為一個模組。

      拿到currentIndex的值後,我們就可以計算三個表格的顯示位置,和每個表格中填充的資料。三個表格我們是通過render函式渲染的,我們根據currentIndex的值來返回不同順序的render函式:

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];
	else if (this.currentIndex === 1) return [table2, table3, table1];
	else return [table3, table1, table2];
}
複製程式碼

      陣列中表格順序不同,反應在頁面上的效果就是不同的先後順序。最後我們通過這個方法得到完整的render:

	renderTable (h) {
      return h('div', {
        style: this.tableWidthStyles
      }, this.getTables(h));
    }
複製程式碼

      然後使用封裝的無狀態的元件,來渲染我們得到的表格render。

<render-dom :render="renderTable"></render-dom>
複製程式碼

      renderDom元件的實現如下:

export default {
  name: 'RenderCell',
  functional: true,
  props: {
    render: Function,
    backValue: [Number, Object]
  },
  render: (h, ctx) => {
    return ctx.props.render(h, ctx.props.backValue, ctx.parent);
  }
};
複製程式碼

      接下來我們講下三個表格中填充的資料的計算。

      我們按照三個模組都在可視區域經過一次算是一輪,通過scrollTop來和currentIndex來計算每個模組當前是在第幾輪展示,但因為我們是從第二個表格才開始做這個邏輯的處理(為了輪播效果更平滑),所以要先判斷當前滾動的高度是否大於一個模組的高度,如果大於才做如下計算:

switch (this.currentIndex) {
   case 0: t0 = parseInt(scrollTop / (this.moduleHeight * 3)); t1 = t2 = t0; break;
   case 1: t1 = parseInt((scrollTop - this.moduleHeight) / (this.moduleHeight * 3)); t0 = t1 + 1; t2 = t1; break;
   case 2: t2 = parseInt((scrollTop - this.moduleHeight * 2) / (this.moduleHeight * 3)); t0 = t1 = t2 + 1;
}
複製程式碼

      計算出每個模組在第幾輪展示後,就可以來取對應的表格資料了:

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);
複製程式碼

      到這裡虛擬渲染的重要內容都介紹完了。表格開發完成後,在專案中實際使用時,當載入二十多萬條資料來測試時,整個頁面卡的讓人無法忍受,資料量越大頁面卡頓越嚴重。我們的表格是沒有問題的,問題出在Vue幫了我們“倒忙”。

      在Vue例項中新增的物件,Vue會先遍歷一遍物件的所有屬性,用Object.defineProperty()為每個物件建立對應的getter和setter。而在專案中,我們的insideTableData只是一個資料集物件中的一個屬性,這個物件還包括很多與這一個資料集相關的資訊,我們在使用this.insideTableData.slice獲取資料的時候會觸發this.insideTableData對應的getter,從而執行一些其他邏輯,而我們的滾動又會頻繁的(僅當currentIndex變化的時候)需要重新填充表格資料,所以這會造成卡頓。

      解決這個問題的辦法就是阻止Vue給我們的資料集物件設定對應的setter和getter,我的方法就是使用ES5的Object.preventExtensions在將資料集物件交給Vue例項代理前將物件密封,這樣資料集物件就變成了不可擴充的了,Vue就不會再新增新的屬性了,也就無法設定setter和getter了。

      做了這個處理後渲染幾十萬資料跟玩兒似的流暢。但是阻止Vue設定getter和setter也造成了一些問題,比如原來表格元件中的一些依賴於表格資料的計算屬性,現在無法在表格資料變化時重新計算,當然了,影響不大,就一個表格行數的計算,所以改成了手動設定這個值。

      到這裡要講的差不多了,這只是專案中的一點優化內容,我封裝的vue-bigdata-table(沒辦法,好名字都被註冊了)表格元件不僅僅這點功能,目前還包括拖動修改列寬、固定列不橫向滾動,固定表頭、內建排序、編輯單元格、貼上、篩選、自定義表頭和單元格等功能。現在也已經開源了,但是還有很多功能還在開發中。

      想玩兒轉Vue技術棧開發,從建立專案開始,到專案部署上線,可以看我的最新視訊教程:《Vue技術棧開發實戰》

      Github連結:github.com/lison16/vue…

歡迎Star。

相關文章