EP26 - 插槽

更新於 發佈於 閱讀時間約 22 分鐘
Slots是什麼卡帶插槽嗎?好想玩任天堂唷www
明年出的Switch2我一定要買一台~扯遠了
Slots是可以在子組件中提供一個可供父組件填充的內容區域

插槽內容與插槽插入點 - Slot Content and Outlet​

我們已經學習了組件可以接受 props,而 props 可以是任何類型的 JavaScript 值。但模板內容呢?在某些情況下,我們可能希望將模板片段傳遞給子組件,讓子組件在其模板中渲染該片段。

例如,我們可能有一個 <FancyButton> 組件,支持如下使用方式:

<template>
<FancyButton>
Click me! <!-- 插槽內容 -->
</FancyButton>
</template>

<FancyButton> 的模板看起來像這樣:

<template>
<button class="fancy-btn">
<slot></slot> <!-- 插槽插入點 -->
</button>
</template>

<slot> 元素是一個插槽插入點,表示應該在何處渲染父組件提供的插槽內容。

插槽示意圖

raw-image

最終渲染的 DOM:

<button class="fancy-btn">Click me!</button>

Try it in the playground

使用插槽後,<FancyButton> 負責渲染外層的 <button>(及其漂亮的樣式),而內部內容則由父組件提供。

另一種理解插槽的方法是將它們與 JavaScript 函數進行比較:

// 父組件傳遞插槽內容
FancyButton('Click me!')

// FancyButton 在其模板中渲染插槽內容
function FancyButton(slotContent) {
return `<button class="fancy-btn">
${slotContent}
</button>`;
}

插槽內容不僅限於文本。它可以是任何有效的模板內容。例如,我們可以傳遞多個元素,甚至其他組件:

<template>
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
</template>

Try it in the playground

通過使用插槽,我們的 <FancyButton> 更加靈活和可重用。我們現在可以在不同地方使用它並提供不同的內部內容,但都具有相同的漂亮樣式。

Vue 組件的插槽機制受到原生 Web Component <slot> 元素的啟發,但具有我們將在後面看到的附加功能。

渲染範圍 - Render Scope

插槽內容可以訪問父組件的數據範圍,因為它是在父組件中定義的。例如:

<template>
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
</template>

在這裡,兩個 {{ message }} 插值會渲染相同的內容。

插槽內容無法訪問子組件的數據。Vue 模板中的表達式只能訪問它所定義的範圍,這與 JavaScript 的詞法作用域一致。換句話說:

父模板中的表達式只能訪問父範圍;子模板中的表達式只能訪問子範圍。

預設內容 - Fallback Content​

有時候在插槽中指定預設內容(即默認內容)是很有用的,這些內容只有在沒有提供插槽內容時才會渲染。例如,在一個 <SubmitButton> 組件中:

<template>
<button type="submit">
<slot></slot>
</button>
</template>

我們可能希望在 <button> 內渲染 "Submit" 文本,如果父組件沒有提供任何插槽內容。為了讓 "Submit" 成為預設內容,我們可以將它放在 <slot> 標籤之間:

<template>
<button type="submit">
<slot>
Submit <!-- 預設內容 -->
</slot>
</button>
</template>

現在當我們在父組件中使用 <SubmitButton>,但沒有提供任何插槽內容時:

<template>
<SubmitButton />
</template>

這將渲染預設內容 "Submit":

<button type="submit">Submit</button>

但如果我們提供了內容:

<template>
<SubmitButton>Save</SubmitButton>
</template>

那麼提供的內容將會被渲染:

<button type="submit">Save</button>

Try it in the playground

命名插槽 - Named Slots

有時在單個組件中擁有多個插槽出口是很有用的。例如,在一個 <BaseLayout> 組件中,其模板如下:

<template>
<div class="container">
<header>
<!-- 我們希望這裡有標頭內容 -->
</header>
<main>
<!-- 我們希望這裡有主要內容 -->
</main>
<footer>
<!-- 我們希望這裡有頁腳內容 -->
</footer>
</div>
</template>

在這些情況下,<slot> 元素有一個特殊的屬性 name,可以用來為不同的插槽分配唯一的 ID,以確定內容應該在哪裡渲染:

<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>

一個沒有 name<slot> 插槽默認被認為是 "default"。

在父組件中使用 <BaseLayout> 時,我們需要一種方式來傳遞多個插槽內容片段,每個片段針對不同的插槽出口。這就是命名插槽的用途。

要傳遞一個命名插槽,我們需要使用 <template> 元素和 v-slot 指令,然後將插槽的名稱作為 v-slot 的參數:

<template>
<BaseLayout>
<template v-slot:header>
<!-- 標頭插槽的內容 -->
</template>
</BaseLayout>
</template>

v-slot 有一個專用的簡寫 #,所以 <template v-slot:header> 可以簡寫為 <template #header>。可以理解為“將此模板片段渲染在子組件的 'header' 插槽中”。

raw-image

這裡是使用簡寫語法將所有三個插槽內容傳遞給 <BaseLayout> 的代碼:

<template>
<BaseLayout>
<template #header>
<h1>這裡可能是一個頁面標題</h1>
</template>

<template #default>
<p>主要內容的一個段落。</p>
<p>再來一個段落。</p>
</template>

<template #footer>
<p>這裡是一些聯繫信息</p>
</template>
</BaseLayout>
</template>

當一個組件同時接受默認插槽和命名插槽時,所有頂層的非 <template> 節點都會被默認視為默認插槽的內容。因此,上述代碼也可以寫成:

<template>
<BaseLayout>
<template #header>
<h1>這裡可能是一個頁面標題</h1>
</template>

<!-- 默認插槽 -->
<p>主要內容的一個段落。</p>
<p>再來一個段落。</p>

<template #footer>
<p>這裡是一些聯繫信息</p>
</template>
</BaseLayout>
</template>

現在,所有在 <template> 元素內的內容都會被傳遞到相應的插槽中。最終渲染的 HTML 將是:

<div class="container">
<header>
<h1>這裡可能是一個頁面標題</h1>
</header>
<main>
<p>主要內容的一個段落。</p>
<p>再來一個段落。</p>
</main>
<footer>
<p>這裡是一些聯繫信息</p>
</footer>
</div>

Try it in the playground

同樣,使用 JavaScript 函數類比可能有助於更好地理解命名插槽:

// 傳遞多個具有不同名稱的插槽片段
BaseLayout({
header: `...`,
default: `...`,
footer: `...`
})

// <BaseLayout> 在不同的地方渲染它們
function BaseLayout(slots) {
return `<div class="container">
<header>${slots.header}</header>
<main>${slots.default}</main>
<footer>${slots.footer}</footer>
</div>`
}

條件插槽 - Conditional Slots

有時候,你會希望根據插槽是否存在來渲染某些內容。

你可以使用 $slots 屬性結合 v-if 指令來實現這一點。

在下面的例子中,我們定義了一個 Card 組件,其中包含三個條件插槽:標頭、頁腳和默認插槽。當標頭、頁腳或默認插槽存在時,我們希望包裝它們以提供額外的樣式:

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

<div v-if="$slots.default" class="card-content">
<slot />
</div>

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

Try it in the playground

動態插槽名稱 - Dynamic Slot Names​

動態指令參數也適用於 v-slot,允許定義動態插槽名稱:

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

<!-- 簡寫 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>

請注意,表達式受動態指令參數的語法限制

作用域插槽 - Scoped Slots

正如在「渲染範圍」中所討論的,插槽內容無法訪問子元件中的狀態。

然而,在某些情況下,我們希望插槽的內容可以同時使用來自父元件和子元件的數據。為了實現這一點,我們需要一種方式讓子元件在渲染插槽時將數據傳遞給插槽。

事實上,我們可以做到這一點 - 我們可以將屬性傳遞給插槽插座,就像將 props 傳遞給元件一樣:

<!-- <MyComponent> 模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>

在單一默認插槽和命名插槽中接收插槽 props 的方式略有不同。我們首先展示如何在單一默認插槽中接收 props,通過在子元件標籤上直接使用 v-slot

<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
raw-image

Try it in the playground

插槽 props 由子元件傳遞,可以作為對應的 v-slot 指令的值,並在插槽內的表達式中訪問。

你可以將作用域插槽想像成傳遞給子元件的一個函數。子元件隨後調用它,並將 props 作為參數傳遞:

MyComponent({
// 傳遞默認插槽,但作為函數
default: (slotProps) => {
return `${slotProps.text} ${slotProps.count}`
}
})

function MyComponent(slots) {
const greetingMessage = 'hello'
return `<div>${
// 用 props 調用插槽函數!
slots.default({ text: greetingMessage, count: 1 })
}</div>`
}

事實上,這與作用域插槽的編譯方式以及如何在手動渲染函數中使用作用域插槽非常接近。

注意 v-slot="slotProps" 與插槽函數簽名的匹配方式。就像函數參數一樣,我們可以在 v-slot 中使用解構:

<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>

命名作用域插槽 - Named Scoped Slots

命名作用域插槽的工作方式類似 - 插槽 props 可以作為 v-slot 指令的值訪問:v-slot:name="slotProps"。使用簡寫時,它看起來像這樣:

<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>

<template #default="defaultProps">
{{ defaultProps }}
</template>

<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>

向命名插槽傳遞 props:

<slot name="header" message="hello"></slot>

注意插槽的名稱不會包含在 props 中,因為它是保留的 - 因此結果 headerProps 將會是 { message: 'hello' }

如果你將命名插槽與默認作用域插槽混合使用,則需要為默認插槽使用顯式的 <template> 標籤。試圖將 v-slot 指令直接放在元件上將導致編譯錯誤。這是為了避免默認插槽的 props 範圍的任何模糊。例如:

<!-- <MyComponent> 模板 -->
<div>
<slot :message="hello"></slot>
<slot name="footer" />
</div>

<!-- 此模板不會編譯 -->
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message 屬於默認插槽,在這裡不可用 -->
<p>{{ message }}</p>
</template>
</MyComponent>

使用顯式的 <template> 標籤為默認插槽有助於明確表示 message prop 在其他插槽中不可用:

<MyComponent>
<!-- 使用顯式默認插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>

<template #footer>
<p>這裡是一些聯繫信息</p>
</template>
</MyComponent>

Fancy List 範例

你可能會想知道作用域插槽的好用例是什麼。這裡有一個例子:假設我們有一個 <FancyList> 元件,它渲染一個項目列表 - 它可能封裝了加載遠程數據、使用數據顯示列表甚至高級功能(如分頁或無限滾動)的邏輯。然而,我們希望它在每個項目的外觀上更加靈活,並將每個項目的樣式留給消費它的父元件。因此,希望的用法可能如下所示:

<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>

<FancyList> 內部,我們可以使用不同的項目數據多次渲染相同的 <slot>(注意我們使用 v-bind 將一個物件作為插槽 props 傳遞):

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

Try it in the playground

無渲染元件

我們上面討論的 <FancyList> 用例封裝了可重用的邏輯(數據抓取、分頁等)和視覺輸出,同時通過作用域插槽將部分視覺輸出委託給消費元件。

如果我們進一步推進這個概念,我們可以創建僅封裝邏輯且不自行渲染任何內容的元件 - 視覺輸出完全通過作用域插槽委託給消費元件。我們稱這種類型的元件為無渲染元件。

一個無渲染元件的例子可能是封裝當前鼠標位置跟蹤邏輯的元件:

<MouseTracker v-slot="{ x, y }">
Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

Try it in the playground

雖然這是一種有趣的模式,但大多數可以通過無渲染元件實現的功能都可以通過 Composition API 以更高效的方式實現,無需額外的元件嵌套開銷。稍後,我們將看到如何將相同的鼠標跟蹤功能實現為可組合的

話雖如此,作用域插槽在我們需要同時封裝邏輯和組合視覺輸出時仍然非常有用,例如在 <FancyList> 範例中。

這篇意外的蠻實用的耶!雖然內容有點多~
大家有空要多練習唷~
原來原生Javascript也有Slot可以用 www


留言
avatar-img
留言分享你的想法!
avatar-img
卡關的人生
2會員
71內容數
分享生活趣事~
卡關的人生的其他內容
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/10
Vue 提供了多種動畫技術來提升應用程式的互動性,包括基於 CSS 類別的動畫、基於狀態的動畫,以及使用監視器來動畫化數值。基於類別的動畫可通過動態添加 CSS 類別來觸發,像是觸發按鈕搖動效果。基於狀態的動畫則是透過樣式綁定,根據互動動態調整元素的外觀,例如根據滑鼠位置改變背景顏色。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/09
Web Components 是一組網頁原生 API,允許開發者創建可重複使用的自訂元素。Vue 與 Web Components 是互補的技術,Vue 支援整合和創建自訂元素。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
2024/11/08
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Thumbnail
看更多
你可能也想看
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
我的「媽」呀! 母親節即將到來,vocus 邀請你寫下屬於你的「媽」故事——不管是紀錄爆笑的日常,或是一直想對她表達的感謝,又或者,是你這輩子最想聽她說出的一句話。 也歡迎你曬出合照,分享照片背後的點點滴滴 ♥️ 透過創作,將這份情感表達出來吧!🥹
Thumbnail
我的「媽」呀! 母親節即將到來,vocus 邀請你寫下屬於你的「媽」故事——不管是紀錄爆笑的日常,或是一直想對她表達的感謝,又或者,是你這輩子最想聽她說出的一句話。 也歡迎你曬出合照,分享照片背後的點點滴滴 ♥️ 透過創作,將這份情感表達出來吧!🥹
Thumbnail
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
Thumbnail
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
Thumbnail
這個範例包含六個基礎範例,接下來的實用範例將會更有趣。第四個範例關於條件與迴圈,實作起來也不難。在介紹這個範例之前,注意到如果組件的 <style> 標籤中缺少 scoped 屬性,樣式會全局生效,影響到其他組件的按鈕樣式。因此,請確保每個組件的樣式都加上 scoped。
Thumbnail
這個範例包含六個基礎範例,接下來的實用範例將會更有趣。第四個範例關於條件與迴圈,實作起來也不難。在介紹這個範例之前,注意到如果組件的 <style> 標籤中缺少 scoped 屬性,樣式會全局生效,影響到其他組件的按鈕樣式。因此,請確保每個組件的樣式都加上 scoped。
Thumbnail
組件組成一棵樹狀結構,類似於嵌套的 HTML 元素,但 Vue 提供了自定義內容和邏輯的封裝。通常我們會在專用的 .vue 文件中定義組件,並使用 <script setup> 來輕鬆管理狀態和事件。組件可以重複使用,並透過 props 傳遞數據,使用插槽實現內容分發。
Thumbnail
組件組成一棵樹狀結構,類似於嵌套的 HTML 元素,但 Vue 提供了自定義內容和邏輯的封裝。通常我們會在專用的 .vue 文件中定義組件,並使用 <script setup> 來輕鬆管理狀態和事件。組件可以重複使用,並透過 props 傳遞數據,使用插槽實現內容分發。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot插槽 ,其中包含默認、具名、語法糖。
Thumbnail
這系列是我在 2023 六角學院 Vue作品實戰班的筆記,筆記以本人理解的方式記錄。此篇主題為 Slot插槽 ,其中包含默認、具名、語法糖。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
在開發 C# Windows Forms 應用程式時,我們經常需要將多個控制項以流式佈局排列,以便在不同大小的視窗或面板中適應佈局變化。這時,FlowLayoutPanel 是一個非常實用的容器控制項,它可以自動調整子控制項的位置,使其按照流式佈局排列。
Thumbnail
在開發 C# Windows Forms 應用程式時,我們經常需要將多個控制項以流式佈局排列,以便在不同大小的視窗或面板中適應佈局變化。這時,FlowLayoutPanel 是一個非常實用的容器控制項,它可以自動調整子控制項的位置,使其按照流式佈局排列。
Thumbnail
這篇文章將會講述使用 C# 的類( Class ) 來讓欄位模組(module)化。
Thumbnail
這篇文章將會講述使用 C# 的類( Class ) 來讓欄位模組(module)化。
Thumbnail
在我們使用的網站中,一個頁面會是由好幾個部份,例如按鈕、菜單、表格、卡片,這些部份可以在不同的畫面重複使用的就稱為元件(Component),今天要跟大家分享的「6個前端工程師私藏的元件庫」!
Thumbnail
在我們使用的網站中,一個頁面會是由好幾個部份,例如按鈕、菜單、表格、卡片,這些部份可以在不同的畫面重複使用的就稱為元件(Component),今天要跟大家分享的「6個前端工程師私藏的元件庫」!
Thumbnail
C# 介面 ( C# Interface ) – (C#教學) – 介面就是類別的接口, 就好像在電插一樣, 不同的電器有同一類與電力的接口. 要編程就像一個布局, 當引用一個class時, 會引用不同的method, property. 如果method的class可以轉換, 就大大簡化了編程.
Thumbnail
C# 介面 ( C# Interface ) – (C#教學) – 介面就是類別的接口, 就好像在電插一樣, 不同的電器有同一類與電力的接口. 要編程就像一個布局, 當引用一個class時, 會引用不同的method, property. 如果method的class可以轉換, 就大大簡化了編程.
Thumbnail
UX文案是設計師們操作情境很好的工具,可以減少用戶認知負擔,讓介面簡單易懂。但我看過許多後台介面的Placeholder文案似乎是亂寫的? 為什麼想寫這個題目? 比起光鮮亮麗的前台介面流程,一般營運管理介面(俗稱後台)經常淪為配角,常被認為是「有空再來談怎麼優化」,而所謂的後台優化常常意味著追加功能
Thumbnail
UX文案是設計師們操作情境很好的工具,可以減少用戶認知負擔,讓介面簡單易懂。但我看過許多後台介面的Placeholder文案似乎是亂寫的? 為什麼想寫這個題目? 比起光鮮亮麗的前台介面流程,一般營運管理介面(俗稱後台)經常淪為配角,常被認為是「有空再來談怎麼優化」,而所謂的後台優化常常意味著追加功能
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News