Skip to content

Vue 條件插槽 (Conditional Slots) 完全指南:原理、實作與排版優化

Vue 3 條件插槽:$slots 與 useSlots

有時候您會需要依據內容是否被傳入插槽來渲染某些內容的技巧。例如:使用者沒有提供內容時,元件的某個區塊就完全不要顯示,甚至連包裝用的 HTML 標籤都消失。

條件插槽的核心原理

Vue 元件實體提供了一個 $slots 物件,它包含了所有父元件傳遞進來的插槽內容。如果父元件有傳入內容,會是一個陣列/函數;如果父元件沒傳入內容,會是 undefined。我們就利用這個特性配合 v-if 來達成條件渲染。

這與預設內容有什麼不同?

基本插槽 的預設內容只能決定 slot 那個位置 要顯示什麼,包在外面的標籤照樣會被渲染出來:

html
<!-- 父元件沒填 head,包裝標籤還在,padding 也還在 -->
<div class="card-head"></div>

條件插槽處理的正是這個問題:沒有內容時,連外層標籤一起不渲染

如何在 Vue 中實作條件插槽?

在下面的案例中,定義了一個卡片元件,內部擁有兩個插槽,分別為 headbody。當 headbody 的內容存在時,希望提供額外的樣式。

選項式 API (Options API)

<template> 中可以直接透過內建的 $slots 全域變數進行判斷。

vue
<template>
  <div class="card">
    <div v-if="$slots.head" class="card-head">
      <slot name="head"></slot>
    </div>

    <div v-if="$slots.body" class="card-body">
      <slot name="body"></slot>
    </div>
  </div>
</template>

組合式 API (Composition API)

如果使用的是 <script setup>,需要透過 useSlots 來存取插槽的狀態。

vue
<script setup>
import { useSlots } from "vue";

const slots = useSlots();
</script>

<template>
  <div class="card">
    <div v-if="slots.head" class="card-head">
      <slot name="head"></slot>
    </div>

    <div v-if="slots.body" class="card-body">
      <slot name="body"></slot>
    </div>
  </div>
</template>

在模板裡其實可以省略 useSlots

<script setup> 的模板同樣能直接使用 $slots,所以單純判斷有無內容時,寫 v-if="$slots.head" 就夠了。useSlots() 的價值在於 需要在邏輯中運用 的時候,例如:整理成 computed 供多處使用:

vue
<script setup>
import { computed, useSlots } from "vue";

const slots = useSlots();
const hasFooter = computed(() => Boolean(slots.foot || slots.extra));
</script>

判斷時容易誤判的地方

$slots 判斷的是 父元件有沒有提供這個插槽,而不是 它渲染出來有沒有東西

vue
<template>
  <Card>
    <!-- 判斷結果為真:template 存在,即使內容是空的 -->
    <template #head></template>

    <!-- 判斷結果為真:v-if 為 false 時仍然提供了插槽函式 -->
    <template #body><p v-if="false">內容</p></template>
  </Card>
</template>

一般專案這樣就夠用了。若您在寫的是元件庫、需要更嚴謹,則要檢查插槽函式回傳的節點是否為空,成本較高,通常不建議一開始就這麼做。

為什麼應該使用條件插槽?

維持乾淨的 DOM 結構

傳統做法常會留下空的 <div></div>。使用 條件插槽 可以確保 HTML 結構與資料狀態完全同步,這對於 SEO 爬蟲理解頁面權重與結構非常有幫助。

精確的樣式控制 (解決 CSS Bug)

這是在排版上最重要的好處。許多元件的包裝層會有 paddingmarginborder。如果內容為空卻渲染了標籤,這些樣式會導致 UI 出現異常的空白間距。

在使用 flex 或 grid 版面時問題更明顯:空標籤依然佔一個位置,gap 也照算,畫面會多出一段莫名的空隙。

提升渲染效能

減少瀏覽器需要處理的 DOM 節點數量,進而減輕樣式計算(Recalculate Style)與排版(Layout)的負擔。

常見問題

$slots 裡面裝的是什麼?

是一個以插槽名稱為 key 的物件,值為回傳虛擬節點的函式。父元件有傳入該插槽時才會有對應的 key,沒有傳入時取到的是 undefined,因此可以直接當條件判斷使用。

$slots 與 useSlots 差在哪裡?

兩者是同一份資料。$slots 可以在模板中直接使用,useSlots() 則是在 <script setup> 中取得它的方式,適合需要在邏輯或 computed 裡判斷的場合。

為什麼不直接用預設內容就好?

預設內容 只能決定 <slot> 位置要顯示什麼,無法移除包在外面的 HTML 標籤。當那個標籤帶有 paddingbordergap 時,即使內容為空也會留下多餘間距,這時就需要條件插槽。

$slots 的判斷結果會隨資料變動更新嗎?

會。$slots 是響應式的,父元件用 v-if 動態決定是否提供插槽內容時,子元件的判斷也會跟著重新計算。

父元件傳了空的 template 也算有內容嗎?

算。只要父元件寫了對應的 <template> 標籤,該插槽的 key 就存在,判斷結果為真。若要更嚴謹地排除實際渲染為空的情況,需要檢查該插槽函式回傳的節點內容。

延伸閱讀