table 組件瞭解兩下?

前言

午後寫的這篇文章,微風加陽光,非常愜意🏖。
而後,話很少說,咱們直接進入正題,今天主要會講解一下可伸縮列、固定列、多級表頭和幾個表格的常見問題✍️,乾貨滿滿哦😯。api

舒適提示:本篇文章是繼上次 table 組件瞭解一下? 這篇文章以後寫的,因此建議先去閱讀一下前面那篇文章👀。固然也能夠直接往下看,由於這裏主要說下思路,沒放多少代碼😁。數組

可伸縮列

可伸縮列,顧名思義就是咱們能夠經過拖動表頭的 border 來實現列寬的大小,看看下面這張圖你就懂了👇: bash

上圖的意思應該表達的挺明瞭,如今咱們簡要看下具體實現步驟👇:

第一步:

咱們在表頭的每一個 th 裏面都多增長一個 div,也就是上圖中紅色的部分,而後絕對定位於 th 的右邊便可。數據結構

第二步:

在表頭 header 和表體 body 同級的地方增長一個 div 來表示上圖中的虛線,默認是隱藏的,就像下面這張圖: app

第三步:

對紅色部分的鼠標事件進行監聽:當鼠標按下的時候,就顯示虛線,並實時改變虛線的 left 值,當鼠標擡起的時候就隱藏虛線,並計算出拖拽後的列寬,以後修改的 columns 裏面對應列的 width 便可,這樣表頭和表體的列寬都會同步改變。 固然,還要記得在鼠標擡起時解綁 mousemovemouseup 事件,這是個好習慣。
以上就是可伸縮列的實現方式。dom

固定列

接下來咱們來看看固定列是怎麼實現的。首先仍是 api 的設計,這個應該容易想到,咱們在 columns 裏面把須要固定的列添加上 fixed 屬性便可,它的值有兩種選擇(leftright),就像下面這樣:函數

columns: [
    {
      title: '姓名',
      key: 'name',
      width: 100,
      fixed: 'left'
    }
    ...
]
複製代碼

爲了簡化問題,這裏咱們只考慮左列固定,由於右列固定也是同樣的。
這裏先一句話說明下原理😁:就是多渲染一份表格並絕對定位在原來的表格之上,下面這張圖應該能幫助你理解👇: post

其實上圖已是實現的核心了,So,咱們就直接說下具體實現方式😎:

第一步:處理表格數據

由於要把須要固定的列放到左邊,因此咱們一開始最須要處理的就是表格數據,把全部有 fixed="left" 屬性的列排在前面。大數據

第二步:渲染兩個表格

再來就是正常渲染兩個表格(A 和 B),事實上表格 A 和表格 B 是如出一轍的,只不過表格 B多設置了一些屬性,好比絕對定位在左上角、定寬(寬度就是有fixed="left"屬性的列的寬度之和)、溢出隱藏啊等。具體代碼結構以下圖所示: this

此外,對於表格 A 的 fixed 部分咱們能夠設置 visibility: hidden,由於它不須要展現,並且 Element 也是這樣寫的;一樣地,對於表格 B 的非 fixed 部分也能夠設置 visibility: hidden
這時候我忽然產生了一個疑問🤔,就是 爲何要設置成 visibility: hidden 而不設置成 display: none 呢?display: none 難道不是能夠渲染更少的 dom 嗎?設置成 visibility 的意義在哪裏?這是個不錯的問題,建議你們思考一下下。。。再往下看😁。

帶着疑問我順便看了下 iView 和 Ant Design 的 dom 結構,發現 iView 也是用 visibility: hidden 來處理的,而 Ant Design 則是直接不渲染了,這就很奇怪啦!因而乎我把 iView 和 Element 的 visibility: hidden 換成 display: none 試了一下,發現好像也 ok,表格展現也是對的,沒什麼問題,因此是爲何呢?其實最主要的緣由是把 visibility: hidden 換成 display: none 會引起行對不齊的問題。
什麼意思呢?就是說若是咱們設置 display: none 的話,表格 A 裏面的行高是不固定的,但這時候表格 B 是沒有展現表格 A 中表體的內容,因此表格 B 不能同步表格 A 裏面的高;而若是咱們設置成 visibility: hidden 的話,表格 A 和表格 B 實際上是都包含全部數據的,只是視覺上不可見而已,這樣它們的行高就可以保持徹底一致,雖然會致使多餘的 dom 元素。
那 Ant Design 爲何能夠呢?其實 Ant Design 也是有這個問題的,雖然它沒有渲染多餘的 dom,可是它會事先計算出表格 A 的行高,而後在去同步設置固定列的行高,此外在表格大小、列寬變化的時候也要去同步。它們是兩種不一樣的方案,你們本身好好體會一下🙌。
固然,這裏咱們採用的是 Element 和 iView 的方案。

第三步:同步滾動

上面的實現方式會有什麼問題呢🤔?最明顯的問題就是表格 A 和表格 B 是割裂的,因此滾動其中一個表格的時候,另一個表格是不會跟着響應的。事實上每一個表格裏面的表頭和表體也是割裂的,因此如今咱們要作就是同步滾動:當咱們橫向滾動表格 A 的表體時,咱們須要同步滾動表格 A 的表頭部分;當咱們縱向滾動表格 A 的表體時,咱們須要同步滾動表格 B 的表體部分。
這裏以表格 A 裏面的表體(A__body)滾動爲例子,簡要說下具體作法: A__body 橫向滾動時:獲取 A__bodyscrollLeft 值,而後把值同步到表格 A 的表頭; A__body 縱向滾動時:獲取 A__bodyscrollTop 值,而後把值同步到表格 B 的表體。 固然滾動是一個高頻動做,因此咱們能夠進行防抖處理;還能夠嘗試用 transform 來替代 scrollTopscrollLeft 進行滾動。

第四步:同步hover

同第三步同樣,當咱們 hover 每一行的時候也須要像滾動同樣進行同步,也就是 hover 樣式的同步。
一樣地咱們以 hover 表格 A 的表體爲例🌰,監聽行的 mouseentermouseleave 事件,當鼠標移入到某一行,這時候你是能獲取該行信息的,而後同步到固定列的對應行便可;同理 hover 到固定列的時候也要同步到表格 A,這裏就再也不贅述。

多級表頭

表頭

首先仍是 api 的設計,咱們但願在 columns 裏面加個 children 就能實現,就像下面這樣(順便看下多級表頭長什麼樣):

columns: [
    {
      title: '日期',
      key: 'date',
      width: 200
    },
    {
      title: '配送信息',
      children: [
        {
          title: '姓名',
          key: 'name',
          width: 200
        },
        {
          title: '地址',
          key: 'addr'
        }
      ]
    }
]
複製代碼

從上圖中咱們能夠看出多級表頭實際上是一行一行來渲染的,每行又能夠分爲一列一列來渲染,實際上就是一個二維數組,兩個 v-for 循環,讓咱們截個 iView 的代碼看看大致結構:
因此咱們首先要作的就是對 columns 進行格式化,使其變成 下面這個樣子(二維數組):
其中每一個數據節點都會給個 levelrowspancolspan,後面兩個是用來實現合併單元格的,好比 columns 的第一項日期,它的 colspan 應該是它 children 的總數,若是沒有 children 就是1;它的 rowspan 應該等於最大層數-當前層數+1,若是有 children 則爲1。
這裏可能會有點繞,因此須要停下來理一理思路🤔,但其實本質就是數據結構的轉換,因此這裏沒有特別強調說明如何轉換,你們本身動手試一試吧😊。

表體

表體渲染就簡單了,只不過咱們一樣要對 columns 進行一下處理,咱們先看看整理以後的列大概長什麼樣:

其實新的 columns 就是遍歷一下舊的 columns,有 children 就繼續遍歷獲取子列,沒 children 就直接取出該列,相似於獲取到全部葉子節點的列,這個新的列會代替原有的 columns 來渲染,因爲這個數據結構轉換簡單點,因此我把代碼貼了上來:

const getAllColumns = (cols, forTableHead = false) => {
    const columns = deepCopy(cols);
    const result = [];
    columns.forEach((column) => {
        if (column.children) {
            if (forTableHead) result.push(column);
            result.push.apply(result, getAllColumns(column.children, forTableHead));
        } else {
            result.push(column);
        }
    });
    return result;
};
複製代碼

其實仍是個數據轉換的問題,你們能夠看下下面兩張圖加深理解👇:

至此,咱們就把可伸縮列、固定列和多級表頭的實現方式給扯完了,哈哈😁😁😁。

問題補充

一、列寬不一致

事實上咱們的表格和表體的列寬其實仍是不一致的,好比當表體有滾動條的時候,表頭沒有,因此就會出現一個滾動條寬度的差距,這時候一般的作法是當縱向滾動條存在的時就在表頭末尾加上一列去彌補這個寬度差,通常咱們稱之爲 gutter。那這個 gutter 的寬度值是多少呢?其實就是滾動條的寬度,那如何計算滾動條的寬度呢?大概原理就是建立一個 div,而後用(無 overflow: scroll 屬性的 div 寬度)-(有 overflow: scrolldiv 寬度),就是滾動條的寬度。

二、錯位

實際開發中,用 Element 的時候若是表格不知道爲啥就錯位了,能夠嘗試用如下方法解決:

this.$nextTick(() => {
    this.$refs['table'] && this.$refs['table'].doLayout()
})
複製代碼

三、卡頓

表格裏若是滿屏都是 tooltipinput 是會卡頓的,畢竟事件監聽和 dom 結構都變多了,因此要避免這種狀況的發生。好比行是編輯狀態的時候才顯示 inputtooltip 改爲實時渲染,不要一開始把每一個都加上 tooltip

四、key 值

v-for 的時候不要用 index 來綁定 key 值,由於 index 多是會改變的,因此並不可靠,儘可能用 id 來綁定。

五、大數據渲染

對於成千上萬行的數據渲染,若是發生了卡頓是很正常的,這時候若是你的表格數據只是用來純展現的話能夠在組件中加上 functional: true 使其成爲函數化組件,這樣能減小渲染開銷。另外一種就是虛擬列表了,雖然說表格數據成千上萬,但可視區的數據就那麼多、範圍就那麼大,咱們永遠只須要渲染可視區域和可視區上下附近的一部分數據便可,其餘不用渲染,而後在滾動的時候根據滾動的多少來計算出當前要顯示的數據區間便可,大致思路就是這個樣子,固然了,It's easier said than done 😂。

小結

吼吼,至此,咱們終於把 table 組件的經常使用功能點講完了,說實話,是真的麻煩,不過但願可以對你有幫助,嘻嘻😄!

相關文章
相關標籤/搜索