2-6-vue框架-组件-vue-component组件化开发-插槽(父传子)
插槽
在之前的章节中,我们已经了解到组件能够接收任意类型的 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' }。