Skip to content

插槽(Slot)

假設有一輛普通的汽車內飾,它包括座椅、方向盤、控制面板等元素。更換這輛汽車的內飾讓汽車看起來與眾不同。可以解釋插槽思想:

  1. 座椅插槽:不同顏色、材質的座椅套件插入這個插槽中。例如:插入紅色皮革套件,使座椅變成紅色;插入藍色絲綢套件,使座椅變成藍色。
  2. 方向盤插槽:在方向盤插槽中插入不同樣式的方向盤套,例如:木質紋理、碳纖維材質等,以改變方向盤的外觀。
  3. 內飾顏色插槽:在內飾顏色插槽中插入不同的顏色選項,例如黑色、銀色或金色,以改變整個內飾的主要色調。

vue
<!-- parent component -->
<template>
  <FancyButton>
    <!-- 把文字作為 slot content 插入 FancyButton -->
    點我!
  </FancyButton>
</template>
<!-- parent component -->
<template>
  <FancyButton>
    <!-- 把文字作為 slot content 插入 FancyButton -->
    點我!
  </FancyButton>
</template>
vue
<!-- child component -->
<template>
  <button>
    <!-- 沒有指定名稱就是 default slot -->
    <slot>
      <!-- 如果父元件沒有指定 slot 的內容,會顯示預設值 -->
      預設值
    </slot>
  </button>
</template>
<!-- child component -->
<template>
  <button>
    <!-- 沒有指定名稱就是 default slot -->
    <slot>
      <!-- 如果父元件沒有指定 slot 的內容,會顯示預設值 -->
      預設值
    </slot>
  </button>
</template>

試試看

存取範圍

Expressions 存取範圍:

  • 寫在 parent template 裡面的 expression 只可存取 parent scope
  • 寫在 child template 裡面的 expression 只可存取 child scope
  • 寫在 parent template 裡面待插入 slot 的語法只可存取 parent scope

具名插槽

html
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
html
<BaseLayout>
  <!-- v-slot:header 把內容綁定到名稱為 header 的 slot -->
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <!-- 沒有包裹的內容預設會顯示在 default slot 裡面 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <!-- #footer 把內容綁定到名稱為 footer 的 slot -->
  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>
<BaseLayout>
  <!-- v-slot:header 把內容綁定到名稱為 header 的 slot -->
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <!-- 沒有包裹的內容預設會顯示在 default slot 裡面 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <!-- #footer 把內容綁定到名稱為 footer 的 slot -->
  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

試試看

動態插槽名稱

可以用程式動態指定內容要插在哪個 slot:

html
<base-layout>
  <template v-slot:[dynamicSlotName]> ... </template>

  <!-- with shorthand -->
  <template #[dynamicSlotName]> ... </template>
</base-layout>
<base-layout>
  <template v-slot:[dynamicSlotName]> ... </template>

  <!-- with shorthand -->
  <template #[dynamicSlotName]> ... </template>
</base-layout>

試試看

作用域插槽(scoped slot)

簡單說:為了讓 slot content 拿到 child 的內容,會讓 child 綁定屬性再做使用,有點像反向注入,在 UI Framework 的元件內大量被使用,必須了解這個才能讀 quasar、vuetify 等等框架的文件。

html
<!-- child -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>
<!-- child -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>
html
<!-- parent -->
<!-- child 綁定的所有屬性會變成一個物件,這裡把它命名 slotProps -->
<template v-slot="slotProps">
  <!-- 這時候可以透過 slotProps 拿到子元件綁定的屬性 -->
  {{ slotProps.text }} {{ slotProps.count }}
</template>
<!-- parent -->
<!-- child 綁定的所有屬性會變成一個物件,這裡把它命名 slotProps -->
<template v-slot="slotProps">
  <!-- 這時候可以透過 slotProps 拿到子元件綁定的屬性 -->
  {{ slotProps.text }} {{ slotProps.count }}
</template>

通常搭配解構賦值使用。

html
<!-- parent -->
<!-- 父元件只需要少數特定的屬性時,可透過解構賦值直接拿到特定屬性 -->
<template v-slot="{ text, count }">
  <!-- 使用時不需每次都透過最上層 slotProps -->
  {{ text }} {{ count }}
</template>
<!-- parent -->
<!-- 父元件只需要少數特定的屬性時,可透過解構賦值直接拿到特定屬性 -->
<template v-slot="{ text, count }">
  <!-- 使用時不需每次都透過最上層 slotProps -->
  {{ text }} {{ count }}
</template>

試試看

Summary

  1. 插槽可以有預設值。
  2. 插槽名稱
    • 插槽名稱可以是字串或動態綁定。
    • 沒有名稱的插槽稱為預設插槽
    • 多個預設插槽父元件需按照順序插入。
  3. 插槽作用域
    • 父元件插入插槽的內容只能存取父元件的狀態。
    • 作用域插槽能讓子元件提供狀態給插槽內容使用。
    • 作用域插槽取值常搭配解構賦值。

Reference