Skip to content

Slots 插槽

說明

「父組件」不只能傳送資料 (props) 給「子組件」,也可以將「渲染內容」傳送過去,讓 「子組件」 透過 <slot /> 在自已模板中渲染這個傳送的「渲染內容」。

圖片出處

在組件中設置的 <slot /> 會提供了一個插槽,會為「父組件」將來傳送的「渲染內容」預留插入的位置。

Demo

🟢 基本 slot 操作

1️⃣ 子組件 => 設置 <slot /> 插槽

html
<template>
  <button>
    <!-- 預留插槽 -->
    <slot />
  </button>
</template>

2️⃣ 父組件 => 傳送「元件」到「子組件」

html
<SlotChirldren>
  <!-- 替換 slot -->
  Click Me!
</SlotChirldren>

3️⃣ 渲染後

html
<button>
  Click Me!
</button>

TIP

  • 若「子組件」無設置 slot ,「父組件」傳送「渲染內容」,直接被丟棄。
  • 若「子組件」有多個 不具名 slot,「父組件」傳送 單一渲染內容 (不具名),會同時顯示在多個 不具名 插槽。
  • 若「父組件」有多個 不具名 內容傳送,會被 合拼 渲染到子組件 slot

渲染作用域

「子組件」 僅負責 組件外層 的編譯,而 slot 的內容是由提供傳送的「父組件」負責編譯,所以 slot 的「渲染內容」,是可以訪問 「父組件」的數據。

Demo

子組件

html
<button class="submit-btn">
  <slot />
</button>

父組件

html
<SlotChirldren>
  <span style="color: red">
    {{ childrenBtnContent }}
  </span>
</SlotChirldren>

js
export default {
  data: () => ({
    childrenBtnContent: 'Click Me!'
  })
}

渲染後

html
<button class="submit-btn">
  <span style="color: red;">Click Me!</span>
</button>

默認內容

當在 slot 設置默認內容,若「父組件」無傳入「渲染內容」,就會顯示「默認內容」。

TIP

常使用在無資料顯示,當沒有傳入資料時,顯示「默認」的設置內容。

設置默認內容

子組件

html
<button class="submit-btn">
  <slot>Disable</slot>
</button>

「父組件」無傳入 slot 內容

js
<SlotChirldren />

渲染後,顯示默認內容

html
<button class="submit-btn">
  Disable
</button>

「父組件」傳入 slot 內容

js
<SlotChirldren>
  Click Me!
</SlotChirldren>

渲染後

html
<button class="submit-btn">
  Click Me!
</button>

🟢 具名 slot 操作

當「子組件」有多個 slot 插槽時,具名的插槽就會非常有用,它可以指定插入的位置。 圖片出處

子組件

html
<template>
  <div>
    <h5>Chirldren component</h5>
    <header>
      <span>Header: </span>
      <!-- 具名插槽 -->
      <slot name="header" />
    </header>

    <button class="submit-btn">
      <slot> disable!</slot>
    </button>

    <main>
      Main:
      <slot name="main" />
    </main>

    <footer>
      <!-- 具名插槽 -->
      <slot name="footer" />
    </footer>
  </div>
</template>

父組件傳送方式

  • 1️⃣ template 方式 v-slot:插槽名稱 / #插槽名稱

    html
    <SlotChirldren>
      <template v-slot:header> <!-- 傳送插槽 name="header" -->
        由父層傳送 header 內容
      </template>
    
      <template #footer>       <!-- 傳送插槽 name="footer" -->
        由父層傳送 footer 內容
      </template>
    
      <h3 slot="main">父層傳送 Main 內容!</h3>
    
      Click Me!
    </SlotChirldren>
    

    WARNING

    這種寫法,必須是 <template /> 標籤。

  • 2️⃣ 任意標籤方式 <tag slot="插槽名稱">渲染內容</tag>

    html
    <SlotChirldren>
      <h3 slot="main">父層傳送 Main 內容!</h3>
    </SlotChirldren>
    

渲染後

html
<div>
  <h5>Chirldren component</h5>
  <header>
    <span>Header: </span>
    <!-- 具名插槽 -->
    由父層傳送 header 內容
  </header>

  <button class="submit-btn">
    <!-- 不具名插槽 -->
    Click Me!
  </button>

  <main>
    Main:
    <h3>父層傳送 Main 內容!</h3>
  </main>

  <footer>
    <!-- 具名插槽 -->
    由父層傳送 footer 內容
  </footer>
</div>

🟢 動態具名 slot 插槽

  • template 方式 v-slot:[動態參數] (簡寫 #[dynamicSlotName])
  • 任意標籤方式 :slot="動態參數"
子組件
html
<template>
  <div>
    <div>
      Dynamic:
      <slot name="dynamic" />
    </div>
  </div>
</template>

父組件

html
<SlotChirldren>
  <template v-slot:[dynamicSlotName] style="color: lightblue">
    <span style="color: lightblue">Parent Pass to Dynamic Content</span>
  </template>

  <!-- 簡寫 -->
  <template #[dynamicSlotName] style="color: lightblue">
    <span style="color: lightblue">Parent Pass to Dynamic Content</span>
  </template>

  <!-- 非 template 寫法 -->
  <span 
    :slot="dynamicSlotName" 
    style="color: lightblue"
  >
    Parent Pass to Dynamic Content
  </span>
</SlotChirldren>

🟢 slot 資料向上傳遞 (Scoped Slots)

渲染內容」只可以訪問到「父組件」的數據 ( 渲染作用域 ),但若需要取得「子組件」數據的話,可以這樣做:

  • 「子組件」設置動態參數傳遞屬性 <slot :傳遞到父層參數名稱="子組件資料" />
  • 「父組件」設置 v-slot="父組件自定義參數名" 來接收數據,「子組件」的 傳遞到父層參數名稱 會在 父組件自定義參數名 之下。

圖片出處

基本範例

子組件

html
<template>
  <div>
    <h1>Slot Scope</h1>
    <slot :num="123" text="slot" />
  </div>
</template>

父組件

html
<SlotScope v-slot="slotData">
  {{ slotData.text }} {{ slotData.num }}
   <!-- slot 123 -->
</SlotScope>

<!-- 解構寫法 -->
<SlotScope v-slot="{ text, num }">
  {{ text + num }}
  <!-- slot123 -->
</SlotScope>

注意

<SlotScope v-slot="slotData"> 這個方法只適用在沒有「具名」的 slot 傳參。

只要內容有 「具名」 渲染內容,就會無法被編譯,這是為了避免默認插槽傳送參數與「具名」插槽參數衝突。

html
<!-- ⛔⛔⛔ 無法編譯 ⛔⛔⛔ -->
<SlotScope v-slot="{ num, text }">
  {{ text + num }}
  <template #header>Header: </template>
</SlotScope>

具名 slot 傳參方法 (使用 template 標籤)

「父組件」使用 template 標籤,可以簡單明白的接收傳遞的傳數。 為了避免「默認」與「具名」的參數資料衝突,建議不直接寫 v-slot 在子組件 SlotScope上,而是直接寫在插槽「渲染內容」上。

子組件

html
<div>
  <h1>Slot Scope</h1>
  <slot :num="123" text="slot" />
  <slot name="header" headerProps="header 傳送的內容" />
</div>

父組件

  • 使用 v-slot="{ 默認 slot 參數名稱 }" 接「默認」 slot 傳送的資料。
  • 使用 #具名插槽名稱="{ 具名 slot 參數名稱 }" 接「具名」 slot 傳送的資料。
html
<!-- es6 解構 -->
<SlotScope>
   <!-- 簡寫 v-slot="{ 默認 slot 參數名稱 }" -->
  <template #default="{ num, text }">{{ text + num }}</template>
  <template #header="{ headerProps }">Header: {{ headerProps }}</template>
</SlotScope>

具名 slot 傳參方法 (使用任意標籤)

不使用 template 的情況下,在「渲染內容」可以使用 slot-scope="{ 子層具名slot參數名稱 }",來接收「子組件」傳遞的參數資料。

子組件

html
  <div style="border: 1px solid lightgreen; border-radius: 20px">
    <h1>Slot Scope</h1>
    <slot :num="123" text="slot" />
    <slot name="header" headerProps="header 傳送的內容" />
    <slot name="main" mainProps="main 傳送的內容" />
  </div>

父組件

html
<SlotScope>
  <template #default="{ num, text }">{{ text + num }}</template>
  <h1 slot="main" slot-scope="{ mainProps }">{{ mainProps }}</h1>
</SlotScope>

進階運用

在「父組件」將請求數據的方法傳遞 props 給「子組件」,「子組件」請求且取得數據資料後,向上傳遞由「父組件」寫入「渲染內容」。

子組件

html
<ul>
  <li v-for="item in items">
    <slot name="item" v-bind="item"></slot>
  </li>
</ul>

父組件

html
<FancyList :api-url="url" :per-page="10">
  <template #item="{ body, username, likes }">
    <div class="item">
      <p>{{ body }}</p>
      <p>by {{ username }} | {{ likes }} likes</p>
    </div>
  </template>
</FancyList>

Reference