插槽
在之前的章節中,我們已經了解到組件能夠接收任意類型的 JavaScript 值作為 props,但組件要如何接收模板內容呢?在某些場景中,我們可能想要為子組件傳遞一些模板片段,讓子組件在它們的組件中渲染這些片段。
含有插槽的子組件
- slot元素是一個插槽出口 (slot outlet),標示了父元素提供的插槽內容 (slot content) 將在哪里被渲染。
<template>
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
</template>
<script>
export default {
}
</script>
含有插槽的父組件
<template>
<FancyButton>
Click me! <!-- 插槽內容 -->
</FancyButton>
</template>
<script>
import FancyButton from './components/demo1.vue'
export default {
components: { FancyButton },
}
</script>
最終渲染出的 DOM 是這樣:
<button class="fancy-btn">Click me!</button>
通過使用插槽,子組件僅負責渲染外層的 button>(以及相應的樣式),
而其內部的內容由父組件提供。
插槽內容可以是任意合法的模板內容,不局限於文本。例如我們可以傳入多個元素,甚至是組件:
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
插槽的好處
通過使用插槽,子組件組件更加靈活和具有可復用性。現在組件可以用在不同的地方渲染各異的內容,但同時還保證都具有相同的樣式。
插槽的渲染作用域
插槽內容可以訪問到父組件的數據作用域,因為插槽內容本身是在父組件模板中定義的。舉例來說:
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
這里的兩個 {{ message }} 插值表達式渲染的內容都是一樣的。
插槽內容無法訪問子組件的數據。Vue 模板中的表達式只能訪問其定義時所處的作用域,這和 JavaScript 的詞法作用域規則是一致的。換言之:
父組件模板中的表達式只能訪問父組件的作用域;子組件模板中的表達式只能訪問子組件的作用域。
插槽的默認內容
子組件
<button type="submit">
<slot>
Submit <!-- 默認內容 -->
</slot>
</button>
父組件
<SubmitButton />
- 當父組件沒有插入內容的時候,子組件就顯示默認內容,
- 但如果我們提供了插槽內容,那么被顯式提供的內容會取代默認內容
多個插槽命名:具名插槽
有時在一個組件中包含多個插槽出口是很有用的。
對於這種場景,slot元素可以有一個特殊的 attribute name,用來給各個插槽分配唯一的 ID,以確定每一處要渲染的內容
沒有提供 name 的 slot出口會隱式地命名為“default”。
含有多個插槽的子組件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
含有多個插槽的父組件
在父組件中使用 BaseLayout 時,我們需要一種方式將多個插槽內容傳入到各自目標插槽的出口。此時就需要用到具名插槽了
要為具名插槽傳入內容,我們需要使用一個含 v-slot 指令的 template 元素,並將目標插槽的名字傳給該指令
v-slot 有對應的簡寫 #,因此 template v-slot:header 可以簡寫為 template #header。其意思就是“將這部分模板片段傳入子組件的 header 插槽中”。
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
動態插槽名
動態指令參數在 v-slot 上也是有效的,即可以定義下面這樣的動態插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 縮寫為 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
在上面的渲染作用域中我們討論到,插槽的內容無法訪問到子組件的狀態。
父組件使用子組件的數據
然而在某些場景下插槽的內容可能想要同時使用父組件域內和子組件域內的數據。要做到這一點,我們需要一種方法來讓子組件在渲染時將一部分數據提供給插槽。
子組件:
在子組件綁定元素:插槽的出口上傳遞 attributes
<template>
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
</template>
<script>
export default {
data(){
return {
greetingMessage: "greetingMessage"
}
}
}
</script>
父組件:默認插槽
<template>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
</template>
<script>
import MyComponent from './components/demo1.vue'
export default {
components: { MyComponent },
}
</script>
我們也可以在 v-slot 中使用解構:
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
父組件:具名插槽
具名作用域插槽的工作方式也是類似的,插槽 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>
注意插槽上的 name 是一個 Vue 特別保留的 attribute,不會作為 props 傳遞給插槽。因此最終 headerProps 的結果是 { message: 'hello' }。
