Vue学习:插槽
1. Vue 插槽
在之前的章节中,我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。这时我们可以使用vue提供的插槽来实现.
举例来说,这里有一个 <FancyButton>
组件,可以像这样使用:
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
而 <FancyButton>
的模板是这样的:
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
<slot>
元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r72LzfpK-1667816154015)(assets/image-20221027173850154.png)]
最终渲染出的 DOM 是这样:
<button class="fancy-btn">Click me!</button>
注意:
插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:
<FancyButton> <span style="color:red">Click me!</span> <AwesomeIcon name="plus" /> </FancyButton>
通过使用插槽,
<FancyButton>
组件更加灵活和具有可复用性。现在组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式。
1.1 具名插槽
上面我们使用的是默认插槽,它是没有名字的,那如果一个组件内部,它将使用多个slot时,那我们怎么知道把某些代码片段放到那个slot中呢?那这个时候,我们就需要使用到具名插槽.所谓的具名插槽,就是给每个slot标签添加一个name属性.
比如:在一个 <BaseLayout>
组件中,有如下模板:
<div class="container">
<header>
<!-- 标题内容放这里 -->
</header>
<main>
<!-- 主要内容放这里 -->
</main>
<footer>
<!-- 底部内容放这里 -->
</footer>
</div>
对于这种场景,<slot>
元素可以有一个特殊的 属性name
,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
这类带 name
的插槽被称为具名插槽 (named slots)。没有提供 name
的 <slot>
出口会隐式地命名为“default”。
在父组件中使用 <BaseLayout>
时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了:
要为具名插槽传入内容,我们需要使用一个含 v-slot
指令的 <template>
元素,并将目标插槽的名字传给该指令:
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
注意:
v-slot
有对应的简写#
,因此<template v-slot:header>
可以简写为<template #header>
。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J9nWPHUg-1667816154019)(assets/image-20221029072123546.png)]
具体代码如下:
BaseLayout.vue
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
export default {
name: 'BaseLayout',
data() {
return {
};
},
mounted() {
},
methods: {
},
};
</script>
<style lang="css" scoped>
.container {
width: 500px;
border: 1px orange solid;
}
</style>
App.vue
<template>
<BaseLayout>
<template v-slot:header>
<h1>登录</h1>
</template>
<template #default>
<form>
<p>
<span class="text">用户名:</span>
<input type="text" v-model="user.username" />
</p>
<p>
<span class="text">密码:</span>
<input type="text" v-model="user.password" />
</p>
</form>
</template>
<template #footer>
<p>欢迎您</p>
</template>
</BaseLayout>
</template>
<script>
import BaseLayout from './components/BaseLayout.vue'
export default {
name:'App',
data() {
return {
user: {
username: '',
password: ''
}
};
},
components:{
BaseLayout,
},
methods:{
}
};
</script>
<style lang="css" scoped>
.text{
text-align: right;
width: 100px;
margin-right: 20px;
}
form p{
display: flex;
flex-direction: row;
}
</style>
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非
<template>
节点都被隐式地视为默认插槽的内容。所以上面也可以写成:<template> <BaseLayout> <template v-slot:header> <h1>登录</h1> </template> <!--默认插槽--> <form> <p> <span class="text">用户名:</span> <input type="text" v-model="user.username" /> </p> <p> <span class="text">密码:</span> <input type="text" v-model="user.password" /> </p> </form> <template #footer> <p>欢迎您</p> </template> </BaseLayout> </template> <script> import BaseLayout from './components/BaseLayout.vue' export default { name: 'App', data() { return { user: { username: '', password: '' } }; }, components: { BaseLayout, }, methods: { } }; </script> <style lang="css" scoped> .text { text-align: right; width: 100px; margin-right: 20px; } form p { display: flex; flex-direction: row; } </style>
注意:
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的,插槽内容无法访问子组件的数据。
在外部没有提供任何内容的情况下,可以为插槽指定默认内容,比如有这样一个
<SubmitButton>
组件:<button type="submit"> <slot></slot> </button>
如果我们想在父组件没有提供任何插槽内容时在
<button>
内渲染“Submit”,只需要将“Submit”写在<slot>
标签之间来作为默认内容:<button type="submit"> <slot> Submit <!-- 默认内容 --> </slot> </button>
现在,当我们在父组件中使用
<SubmitButton>
且没有提供任何插槽内容时:<SubmitButton />
“Submit”将会被作为默认内容渲染:
<button type="submit">Submit</button>
但如果我们提供了插槽内容:
<SubmitButton>Save</SubmitButton>
那么被显式提供的内容会取代默认内容:
<button type="submit">Save</button>
1.2 作用域插槽
在上面的[注意]中我们讨论到,插槽的内容无法访问到子组件的状态.然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
1.2.1 默认作用域插槽
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot
指令,直接接收到了一个插槽 props 对象:
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMsHuKmX-1667816154028)(assets/image-20221029091310534.png)]
1.2.2 具名作用域插槽
具名作用域插槽的工作方式也是类似的,插槽 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' }
。
如果你混用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template>
标签。尝试直接为组件添加 v-slot
指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYTlaKFE-1667816154032)(assets/image-20221029095440574.png)]
正确的写法:
<MyComponent >
<template #default="a">
<p>
{{a.msg}}
</p>
<p>
{{a.count}}
</p>
</template>
<template #header>
<p>你好</p>
</template>
</MyComponent>