DMap(諦聽)——實戰Vue百萬條數據渲染表格組件開發

      近幾個月在開發一個基於Vue的數據可視化分析輔助應用———DMap(諦聽),一套爲數據分析師和數據科學家提供的基於位置大數據分析的工具,旨在提升數據分析效率,下降獲取多數據並行分析成本,簡化大屏和數據報告開發製做流程。UI組件使用的是iView,地圖可視化庫使用的是inMap,服務端使用Node.js搭建。 前端

      DMap的核心就是服務大數據分析,因此當面對幾萬幾十萬甚至百萬級別的數據時,性能優化是一個具備挑戰性的問題。今天我就拿項目中一個表格渲染的優化爲例來展開介紹。vue

      在前端開發中,用表格來展現數據是再日常不過的了,當數據量較多時,咱們一般的作法是使用分頁,若是數據量不算太多隻有兩三頁,咱們大能夠把全量數據獲取下來,在前端作簡單的分頁展現。當數據量再上一個等級時,咱們就須要根據頁數向服務端請求這一頁須要的數據。可是DMap做爲助力大數據可視化的分析工具,咱們須要將全量的數據在前端作展現,而爲了提高用戶體驗,咱們在表格的展現上決定不作分頁,也不作懶加載,而是像Excel那樣能夠無縫隙的滾動。git

      在Web中,長列表渲染的性能問題已經有一些成熟的方案,表格和長列表類似,當渲染的行數達到必定量時,滾動就會變得卡頓,因此咱們使用了虛擬渲染的方案,就是隻渲染用戶所能看到的區域的一小部分數據,而後經過滾動來計算顯示的數據,和上下佔位元素的高度。 github

      經過這個圖能夠對原理有個大概的瞭解,接下來講下計算上的細節。segmentfault

      首先咱們須要監聽表格外層容器(也就是顯示滾動條的元素)的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:bash

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

      這的top就是更新後的this.scrollTop的值,moduleHeight是單個表格的高度,咱們稱它爲一個模塊。dom

      拿到currentIndex的值後,咱們就能夠計算三個表格的顯示位置,和每一個表格中填充的數據。三個表格咱們是經過render函數渲染的,咱們根據currentIndex的值來返回不一樣順序的render函數:ide

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。

相關文章
相關標籤/搜索