iT邦幫忙

2025 iThome 鐵人賽

DAY 18
2
Vue.js

在 Vue 過氣前要學的三十件事系列 第 18

在 Vue 過氣前要學的第十八件事 - 我們必須更深入一點 / slot

  • 分享至 

  • xImage
  •  

前言

在設計元件時使用 slot,可以為元件設計帶來很高的彈性。

並減少過度拆元件造成的傳遞參數問題,也就是 props drilling

常見使用方法有 <slot>useSlots(),以及我們會帶到 slot 的運作原理。

使用

<slot>

不具名插槽slot 顧名思義就是一個插槽的意思,
你把要嵌入的組件或文字放入設計好的元件中。

那 <slot></slot> 將會被替換成你所放入的東西
也就是說 <slot> 是起到一個佔位符的作用

https://ithelp.ithome.com.tw/upload/images/20250917/20172784uWxBREBwUO.png

<FancyButton>
  Click me! <!-- 這是你要塞入的文字或組件 -->
</FancyButton>

父組件

<button>
  <slot></slot>
</button>

子組件

具名插槽

https://ithelp.ithome.com.tw/upload/images/20250917/20172784x6nqnGUuNU.png
上面的程式碼中只提供了一個 <slot></slot> 插槽,
那如果我今天要有多個插槽呢?

譬如下面這樣 :

<div class="container">
  <header>
    <!-- 組件的 header 位置 -->
  </header>
  <main>
    <!-- 組件的 main 位置 -->
  </main>
  <footer>
    <!-- 組件的 footer 位置 -->
  </footer>
</div>

元件內尚未使用插槽

這個時候具名插槽就很好用了, 
<slot> 元素有一個特殊的 attribute name, 作用類似於插槽的唯一 ID

<div class="container">
  <header>
    <slot name="header"></slot> 
  </header>
  <main>
    <slot></slot> <!-- 這邊沒有給名字,所以預設名字是 "default" -->
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

元件內使用具名插槽和未具名插槽

<BaseLayout>
  <template v-slot:header>
    <!-- content for the header slot -->
  </template>
</BaseLayout>

父組件使用有具名插槽的元件

通常情況下 沒有 name的,會被隱式的命名為 default,
透過不具名 傳入的組件會被預設放在 default 插槽裡

useSlots()

這邊其實官網的說明還蠻 "簡潔"

Returns the slots object from the Setup Context, which includes parent passed slots as callable functions that return Virtual DOM nodes.

簡單來說就是回傳 slots 物件讓你可以做一些額外操作
例如說下面這個情境:

<script setup>
import { useSlots } from 'vue'

const slot = useSlots()

const hasHeader = slot.header;
const hasFooter = slot.footer;
</script>

<template>
 <div class="container">
   <div v-if="hasHeader" class="mb-1">
      <slot name="header"></slot>
   </div>
   <div v-if="hasFooter" class="mb-1">
      <slot name="footer"></slot>
   </div>
 </div>  
</template>

如果沒有某個欄位的話,你不會希望他有莫名其妙的 margin,
如果這邊沒做這個操作,你可以會覺得說:

喔 ~ 我不傳 slot 那應該就不會 render 了吧。
https://ithelp.ithome.com.tw/upload/images/20250917/20172784agFD782VUu.jpg

但外面那層 div 還是存在鴨。

slot 運作原理

slot 是一個我覺得非常直觀的使用方法,因為你放什麼他就會出現什麼,
但他背後是怎麼 work 的呢?

<!-- App.vue-->
<template>
  <section>
    <Comp>
      <h1>鐵人賽</h1>
    </Comp>
  </section>
</template>
<!-- Comp.vue-->
<template>
  <div class="container"></div>
</template>

各位覺得這個會出現什麼呢?

https://ithelp.ithome.com.tw/upload/images/20250917/20172784UJauKBV8o2.png
沒錯 nothing

但為什麼我們放個 <slot> 就可以把這些 HTML 傳進去,
我們來看一下編譯後的渲染函數是長怎樣吧。

<template>
  <div class="container">
    <slot ></slot>
  </div>
</template>

Comp.vue 編譯前

// 略
import { renderSlot as _renderSlot, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

// 略
function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", _hoisted_1, [
    _renderSlot(_ctx.$slots, "default")
    // slot 標籤在這裡被編譯成插槽函數
  ]))
}
// 略

Comp.vue 編譯後

function renderSlot(slots, name, props = {}, fallback, noSlotted) {
  if (/*略...*/) {
    if (name !== "default") props.name = name; // 具名插槽替換 default 
    // 回傳 virtual DOM 相關工具函數
    return openBlock(), createBlock(
      Fragment,
      null,
      [createVNode("slot", props, fallback && fallback())],
      64
    );
  }
// 略...

renderSlot 原始碼

一路從模板語法 <template> 編譯成渲染函數,
再根據渲染函數中的插槽函數了解原始碼運作原理,
所以一句話總結, slot 並不是什麼魔法,就只是又包了一層插槽函數

結語

其實原本以為自己已經都知道 slot 怎麼寫,
不過認真翻過文章後還是發現一些自己沒注意到的用法。

今天我們從基礎的 slot 用法,具名插槽的用法,
useSlots() 來協助我們做一些條件判斷操作,
以及最後的插槽運作原理程式碼。

今天是我們組件溝通篇章的最後一篇,明天將進入更進階的套件使用環節,
相信經過這段時間你應該已經能了解最基本的使用方法,甚至能根據情境判斷該用何種 API,

那真實的專案並不只會靠自己純手刻,因為時間限制的原因,
使用別人造好的輪子當然能節省我們很多困擾。

跟前面的文章一樣,你將會學習到不局限於 Vue 的觀念及實作。
如果你喜歡這個系列或是想看我發瘋,歡迎按下 訂閱 一起走完這三十天吧。

一些小練習

  1. 試著用一句話講解 slot
  2. 什麼情況要用 slot ? 什麼情況用 props 就能解決了
  3. 什麼情況會用到具名插槽,預設情況下是什麼名稱 ?
  4. useSlot 可以用在什麼情況?


上一篇
在 Vue 過氣前要學的第十七件事 - 依賴注入 Provide/Inject
系列文
在 Vue 過氣前要學的三十件事18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言