iT邦幫忙

2021 iThome 鐵人賽

DAY 18
3
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 18

不只懂 Vue 語法:什麼是 slot?請示範 slot 的用法?

  • 分享至 

  • xImage
  •  

問題回答

slot(插槽)的概念是把外層的內容塞進子元件的指定位置裏。跟插槽的字面意思一樣,前提是:有插口才能插。子元件需要開一個插口(slot),才可以在外層元件把內容塞進子元件裏。

slot(插槽) 可分為四種:

  • slot(插槽)
  • Named slot(具名插槽)
  • Scoped slot(作用域插槽)
  • Dynamic slot(動態插槽)

以下會再作詳細解說。

slot(插槽)

先講解第一個最簡單使用 slot 的方法。

假設有一個叫 Card 的元件,我們想要從父層,把「夏日短裙」這文字塞進這個元件(即是子層),就可使用slot

結果:

以上就是 slot 概念:在外層把內容塞到子元件的指定位置裏。這就是預設插槽,也是最簡單的用法。

預設內容

避免沒有傳入內容,可以設定顯示預值內容:

App.vue

<template>
  <Card></Card>
</template>

Card.vue

<template>
  <h1>
    <slot>這是預設文字</slot>
  </h1>
</template>

以上結果就會渲染「這是預設文字」。

Named slot (具名插槽)

借用這篇文章的解說例子,用下圖就能理解為什麼要使用具名插槽:


圖片來源:https://dev.to/samanthaming/how-i-m-using-vue-slots-on-my-site-nfn

假設有一個有很多工具的工具箱(父層內容),現在要把它們放到一個盒子裏的不同間隔範圍裏(子元件)。這時候,盒子裏要標示清楚這個範圍是放什麼工具。正如在子元件裏,要使用具名插槽來指定父層的內容要被指派塞進什麼地方。

例子:

結果:

注意:如果 slot 沒有命名,在父元件傳入內容時,需使用 v-slot:default 來標示。

另外,縮寫可以用 # 代替 v-slot:

<Card>
    <template #title>
      <h1> 夏日短裙 </h1>
    </template>
    <template #price>
      <p> $200 </p>
    </template>
    <template #default>
      <p> Sizes: XS, S, M, L </p>
    </template>
</Card>

為什麼用 slot?與 props 有什麼分別?

在進入介紹最後一個 slot 用法,scoped slot 之前。先理解為什麼需要用 slot。props 與 slot 的分別是:

  • props 主要是用作傳資料,不是傳入 DOM 內容。例如,如果把<p> $200 </p> 這個 DOM 內容轉入子元件裏,這情況用 slot 就會方便很多。
  • 有時候資料是靜態,不常變動,甚至需要大量重複。這情況無必要使用 props。
  • props 無法像 slot 分配資料呈現的位置。

利用 slot 打造自己的 UI 元件

參考 Mike 老師這個影片,他有提到使用 slot 打造自己的 UI 元件。過程就如自行製作一個 Vue 模版,利用 named slot 預先在分配資料的位置。當父層傳入資料時,就自動把父層的資料,分配到子元件的不同位置裏。

舉例說,如果一個網頁的每個 section heading 都有同一個樣式,我可以把 heading 拆成一支獨立檔案。再同時利用 named slot 分配好不同資料的位置。這樣的好處是:

  • Card 的 CSS 能拆出來寫,不用寫在父層裏。
  • 比起使用 props,能自動分配資料到不同位置作呈現。

Heading.vue

<template>
  <div class="section">
    <h2>
      <slot name="title"></slot>
    </h2>
    <p>
      <slot name="slogan"></slot>
    </p>
  </div>
</template>

App.vue

<Heading v-for="section in sections" :key="section.title">
    <template #title>
      {{ section.title }}
    </template>
    <template #slogan>
      {{ section.slogan }}
    </template>
</Heading>
export default {
  components: {
    Heading,
  },
  data() {
    return {
      sections: [
        {
          title: "限時優惠",
          slogan: "搶購限時最便宜貨品!",
        },
        {
          title: "最新商品",
          slogan: "為你帶來最新貨品!",
        },
        {
          title: "一般商品",
          slogan: "多種商品任你選擇!",
        },
      ],
    };
  },
};

結果:

完整程式碼

https://codesandbox.io/s/vue-slot-named-slot-zi-zhi-ui-yuan-jian-y7ej4?file=/src/components/Heading.vue

Scoped slot(作用域插槽)

Scoped slot 是指把子元件的資料取出來,給父層使用。這是因為父層無法取得子元件的資料。

舉例說,每個商品都用一個 Card 元件來呈現。商品資料在父層,而 Card 的資料呈現和樣式就在 Card 元件裏面。像以下結構:

App.vue

<Card>
    <template #title>...</template>
    <template #price>...</template>
    <template #size>...</template>
</Card>
export default {
  components: {
    Card,
  },
  data() {
    return {
      items: [
        {
          title: "夏日短裙",
          price: 200,
          sizes: ["XS", "S", "M", "L"],
        },
        {
          title: "純白 T-shirt",
          price: 100,
          sizes: ["XS", "S"],
        },
        {
          title: "針織背心",
          price: 300,
          sizes: ["XS", "S", "M"],
        },
      ],
    };
  },
};

components/Card.vue

  <div class="card">
    <h1 class="title">
      <slot name="title"></slot>
    </h1>
    <p>
        售價:
      <slot name="price"></slot>
    </p>
    <p>
      <slot name="size"></slot>
    </p>
    <div class="quantity">
      <label for="quantity">數量:</label>
      <select name="quantity" id="quantity" v-model="quantity">
        <option value="0">0</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
    </div>
    <p class="result">
      <slot name="result" :quantity="quantity" ></slot>
    </p>
  </div>
export default {
  data() {
    return {
      quantity: 0
    }
  }
};

結果每個 Card 都會長這樣:

在這情況中,數量的 select 在子元件(Card.vue)裏。可是,父層需要利用子元件 select 的結果,組成:已選購:{{ item.title }} x {{ quantity}} 這字串,再塞回子元件的 <slot name="result"></slot> 裏面。

解決方法:

在子元件(Card.vue) 的 slot 裏綁定 prop(我們稱為 slot props),然後在父層的相應 template 裏取出來用,並且組字串,塞回子元件裏。

父層 App.vue

<template #result="slotProps">
  已選購:{{ item.title }} x{{ slotProps.quantity }}
</template>

子元件 Card.vue

<p class="result">
  <slot name="result" :quantity="quantity"></slot>
</p>

注意,props 名稱不一定叫 slotProps,可自定義其他名稱。再簡潔一點,使用解構寫法:

<template #result="{ quantity }">
  已選購:{{ item.title }} x{{ quantity }}
</template>

完整程式碼

https://codesandbox.io/s/scoped-slot-yong-fa-dxqh4?file=/src/App.vue:320-416

Vuetify 經常使用 scoped slot

Vuetify是一個提供給Vue開發者的material UI 框架。

它有大量使用Scoped slot,讓開發者客製化Vuetify裏封裝好的元件,以 table 這元件為例。這裏有使用scoped slot,指定要更改Calories這個item裏的內容:

Dynamic slot(動態插槽)

動態插槽很簡單,就是在父層可以動態指定不同 slot,把內容塞到子元件裏。

父層,使用中括號動態指定 slot:

<template>
  <div v-for="area in areas" :key="area">
    <input type="radio" :id="area" :value="area" v-model="chosenArea">
    <label :for="area"> {{ area }} </label>
  </div>
  <Color>
    <template v-slot:[chosenArea]> 
      <span>我現在在: {{ chosenArea }}</span>
    </template> 
  </Color> 
</template>
data() {
    return {
      chosenArea: 'nav',
      areas: ['nav', 'main', 'footer']
    }
}

子層,動態建立slot:

<p v-for="area in areas" :key="area" :class="area">
    <slot :name="area"></slot>
</p>
export default {
  data() {
    return {
      areas: ['nav', 'main', 'footer']
    }
  }
}
<style>
.nav {
  color: red;
}

.main {
  color: black;
}

.footer {
  color: blue;
}
</style>

結果

完整程式碼

https://codesandbox.io/s/dynamic-slot-shi-fan-4yenc

總結

  • slot(插槽)的作用是把父層內容,塞到子元件裏。有插口才能插,所以,子元件要先建立 slot,父層才可以把資料塞進去。
  • 常用情景是,利用 slot 打造自己的 UI 模版,把靜態資料在父層塞進去這模版中。非常適合用於大量複製同樣樣式元件,以及傳入靜態資料的情況。
  • 使用 named slot(具名插槽)可以把資料分配到指定位置。
  • 使用 scoped slot(作用域插槽)可以把子元件裏的資料,傳出去給父層使用和處理,之後透過插槽塞回到子元件裏。
  • 使用 dynamic slot(動態插槽)可以在父層動態指定插槽。

參考資料

Vue.js: slots vs. props
How I'm using Vue Slots on my site
The Difference Between Props, Slots and Scoped Slots in Vue.js
重新認識 Vue.js - 2-4 編譯作用域與 Slot 插槽
Vue slots 使用超入門
複用元件的好幫手:Vue Slots(v-slot、Scoped Slots)


上一篇
不只懂 Vue 語法:什麼是 directive?請示範如何使用 directive?
下一篇
不只懂 Vue 語法:請示範如何使用 Vue 3 的 teleport?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言