iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

Vue.js 進階心法系列 第 29

表格元件共用攻略

前面講完表單這樣常見又複雜的製作方式,其實在對付的是一種「以物件為根的」資料結構。並且同時又處理掉了物件的物件,以及物件的陣列。那麼所有的表單其實就沒有什麼好害怕的了,是吧?(是嗎?)

那麼,如果以要處理「以陣列為根的」資料結構呢?通常,我們會使用 UI component 來處理這個問題。哈哈~~

這一次,我就以 BootstrapVue 為例。(這一招可以適用於其它 UI component 唷!)
來看看我們會遇到什麼難題吧!

列表

Table | Components | BootstrapVue 這個元件為主角

要處理一個這樣的資料

[
  { age: 40, first_name: 'Dickerson', last_name: 'Macdonald' },
  { age: 21, first_name: 'Larsen', last_name: 'Shaw' },
  { age: 89, first_name: 'Geneva', last_name: 'Wilson' },
  { age: 38, first_name: 'Jami', last_name: 'Carney' }
]
[
  {
    key: 'first_name',
    label: '名',
  },
  {
    key: 'last_name',
    label: '姓',
  },
  {
    key: 'age',
    label: '年紀',
  },
]

設定到表格 component 時,其實就是放進兩個

<b-table striped hover :items="items" :fields="fields"></b-table>
  • striped
  • hover

畫面像這樣

需求

  • 如果整個站,不只是 User 這個 Array 呢?共用的只有 Array 操作,而元素內的型別需要各別處理。
  • 想要讓整個網站的 table 吃相同的設定、想改時,可以一起改

這樣的需求,是不是能夠做一個 list 的 component 了呢?

共用表格 component

src/components/list.vue

<div class="list">
  <b-table striped hover :items="items" :fields="fields"></b-table>
</div>
export default {
  name: 'List',
  props: {
    items: {
      type: Array,
      required: true
    },
    fields: {
      type: Array,
      required: true
    }
  }
}

src/views/Users.vue

<List :items="$store.getters.users" :fields="fields"></List>

src/views/Departments.vue

<List :items="$store.getters.departments" :fields="fields"></List>

做完了。謝謝 還沒完!

咦?我們不是做到這兩點了嗎?

  • 如果整個站,不只是 User 這個 Array 呢?共用的只有 Array 操作,而元素內的型別需要各別處理。
  • 想要讓整個網站的 table 吃相同的設定、想改時,可以一起改

但是,如果我們想要客製欄位,怎辦?

Slots - Table | Components | BootstrapVue

問題是什麼?

在我們重新封裝了 UI Component 的元件之後,是不是就受限了使用方式了呢?
還可以依照官網的說明使用我們封裝過的元件嗎?

這樣一來,就可以封裝「共用特定的使用方式」,依官網的使用方式,又可以依不同的資料客製使用方式。

聽不太懂?!沒關係,先看結果,究竟是要做到什麼效果呢?

技術需求

  1. 封裝對於 table 的 props 設定。
  2. 客製欄位渲染方式,使用的是 slot ,要在自己封裝好的元件,使用 bootstrap 的 slot

Custom data rendering 這一段有介紹怎麼樣寫。而最終就是想要照著寫。

記住,任何自己發明的 props、slot 最好都要寫一份文件,不要以為別人都看得懂你的設計。QQ
因為我不想寫文件,所以致力於「別人的文件,就是我的文件」
只要用法和 UI Component 一樣,基本上就可以使用它強大的功能與文件。

<List :items="$store.getters.users" :fields="fields">
  <template #cell(name)="{ item }">
    {{ item.last_name }} {{ item.last_name }}
  </template>
</List>
[
  {
    key: 'name',
    label: '姓名',
  },
  {
    key: 'age',
    label: '年紀',
  },
]

透過前面介紹的技巧知道 $attrs 和 $listeners 可以在自訂的 component 上使用 UI Component 。但是,<slot> 呢?

找適合的 API

<div class="list">
  <b-table striped hover :items="items" :fields="fields">
    <!-- 在這裡究竟要放什麼呢? -->
  </b-table>
</div>

探索之前,先印出來看

export default {
  name: 'List',
  mounted() {
    console.log('在這裡印出要測試的 API')
  }
}

如果,你用的是 Vue2 可以試試 $scopedSlot

<div class="list">
  <pre>{{ Object.keys($scopedSlot) }}</pre>
</div>

export default {
  name: 'List',
  mounted() {
    console.log(this.$scopedSlot)
  }
}

如果,你用的是 Vue3 可以試試 $slots

<div class="list">
  <pre>{{ Object.keys($slots) }}</pre>
</div>

export default {
  name: 'List',
  mounted() {
    console.log(this.$scopedSlot)
  }
}

總之,我們得了一個長得像這樣的東西

[ "cell(name)" ]

怎麼用呢?

由於因為 BootstrapVue 還不支援 Vue3 所以剩下部份,我們就用 Vue2 實現吧! (目前看起來就 API 不太一樣而已)

<b-table :items="items" :fields="fields">
  <template v-for="(value, key) in $scopedSlots" v-slot:[key]="{ item }">
    <slot :name="key" v-bind:item="item"></slot>
  </template>
</b-table>

這樣一來,原本我們寫成這樣的東西

<b-table striped hover :items="$store.getters.users" :fields="fields">
  <template #cell(name)="{ item }">
    {{ item.last_name }} {{ item.last_name }}
  </template>
</b-table>

就可以寫成下面這兩個組合

src/views/users.vue

<List :items="$store.getters.users" :fields="fields">
  <template #cell(name)="{ item }">
    {{ item.last_name }} {{ item.last_name }}
  </template>
</List>

src/components/list.vue

<b-table striped hover :items="items" :fields="fields">
  <template v-for="(value, key) in $scopedSlots" v-slot:[key]="{ item }">
    <slot :name="key" v-bind:item="item"></slot>
  </template>
</b-table>

成功的將 bootstrap-vue 的 table,其中的 slot 移植到我們自己的 component 上使用。
這樣一來,還有什麼可以阻止得了我呀!哈哈哈

之後,不管是 props, events 甚至是 slot 都可以任意使用啦,任何的 component 我就可以自由的封裝共用的設定,而開放非共用的設定。包裝成自己想要的 component 了!

最後一個資料駁動的拼圖: 表格 = 是 Array 裡的 Object

我們實作過下面這四種變化了

  • Object
  • Object 裡的 Object
  • Object 裡的 Array
  • Array 裡的 Object

未來遇到任何奇奇怪怪的資料結構,就完全不用害怕囉!
不用再說「呃...後端這個結構太複雜,是不是可以拆開做呢?」

最後,你覺得這一系列有進階嗎?
歡迎留言告訴我。

也歡迎你留下自己習慣的寫法 (repo),與我分享唷!


上一篇
後端說修改時只需要送「有修改的欄位」過來
下一篇
完賽!YA!關於 Vue.js 進階心法系列
系列文
Vue.js 進階心法30

尚未有邦友留言

立即登入留言