个人对插槽的理解
插槽是子组件提供给父组件修改自身某个位置的内容、或在某个位置添加内容的一种手段。形象来讲就是组件定义时会在指定位置留下一些坑位,父组件调用时可以按需往坑里面填充一些内容或修改默认内容。
插槽分为匿名插槽和具名插槽两种类型,实际上匿名插槽也是有名字的,匿名插槽的名字是
default
。
匿名插槽使用示例
子组件是一个按钮组件,通过内置组件
slot
指定插槽位置,用于展示按钮文字描述。
<template>
<button class="my-button" v-on="$listeners" v-bind="$attrs">
<slot></slot>
</button>
</template>
<script>
export default {
name: "myButton",
</script>
<style lang="css">
.my-button {
outline: none;
color: rgb(206, 17, 17);
background: #fff;
border: 1px solid rgb(206, 17, 17);
padding: 4px 8px;
font-size: 15px;
border-radius: 4px;
cursor: pointer;
.my-button:disabled {
color: #999;
border: 1px solid #999;
cursor: not-allowed;
</style>
父组件调用时直接在组件标签内填充按钮文字内容即可,用法和原生按钮保持一致。
小提示: 插槽填充内容不限制一定是文字,也可以是其他html元素或者vue组件(不限个数),只是本例中的需求是填充文字而已。
<template>
<div class="app-container">
<button @click="handleClick">原生按钮</button>
<button disabled>原生禁用按钮</button>
<my-button @click="handleClick">好看的按钮</my-button>
<my-button disabled>好看的禁用按钮</my-button>
<my-button>
<div>可以填充一个div</div>
</my-button>
</div>
</template>
<script>
import myButton from "./button.vue"
export default {
name: "App",
components: {
myButton
methods: {
handleClick(e) {
console.log(e)
</script>
<style lang="css">
button+button {
margin-left: 10px;
</style>
效果展示:
具名插槽使用示例
子组件是一个卡片组件,卡片分为头部和内容两部分,头部使用具名插槽,内容使用匿名插槽,具名插槽通过name
属性指定插槽名称。
在这个组件中,由于slot
组件无法添加class属性,我们为了预设定头部的样式,将头部插槽包裹在一个div里面了,但是如果在父组件没使用头部插槽时,我们希望子组件隐藏包裹插槽的div,所以这里使用vue实例属性$slots
加了一个判断,这样就可以达到隐藏包裹元素的作用。
<template>
<div class="my-card">
<div class="header" v-if="$slots.header">
<slot name="header"></slot>
</div>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "myCard",
</script>
<style lang="css">
.my-card {
margin: 10px;
border: 1px solid #eee;
box-shadow: 0 0 15px 3px rgba(0, 0, 0, .3);
.my-card .header {
border-bottom: 1px solid #e8e8e8;
padding: 8px 10px 8px 20px;
font-weight: bold;
position: relative;
.my-card .header::before {
content: "";
width: 4px;
height: 16px;
position: absolute;
left: 8px;
top: 50%;
margin-top: -8px;
background-color: #fd2323;
.my-card .content {
padding: 10px;
</style>
父组件使用子组件插槽有两种写法,推荐使用新语法
旧语法是使用属性来指定插槽位置:slot="插槽名称"
新语法(v2.6以上)是使用指令来指定插槽位置:v-slot:插槽名称
,而且v-slot
指令只能作用于template
组件上
<template>
<div class="app-container">
<my-card>
<div slot="header">使用slot属性指定要插到头部插槽位置</div>
<div>我是内容,不需要指定插入位置</div>
</my-card>
<my-card>
<template v-slot:header>
<div>使用slot属性指定要插到头部插槽位置</div>
</template>
<div>我是内容,不需要指定插入位置</div>
</my-card>
<my-card>
<div>我是内容,这个卡片不显示头部</div>
</my-card>
</div>
</template>
<script>
import myCard from "./card.vue"
export default {
name: "App",
components: {
myCard
</script>
效果展示:
具名插槽的name属性也可以是动态绑定的,动态的name属性在配合v-for
使用时可以搭配出很灵活的写法。
默认显示内容
在父组件没有传入插槽内容时,我们可以在子组件的插槽位置显示一个默认的内容,例如上文的按钮组件,我们可以在slot
标签中间加入提交
两个字,父组件调用时不传文字,那么这个按钮的文字就显示为提交
。
子组件代码
<slot>提交</slot>
父组件代码
<my-button></my-button>
显示效果:
该功能在具名插槽上用法一致。
插槽传值(作用域插槽)
slot
组件跟普通组件一样可以使用v-bind
指令绑定prop
属性,父组件可以通过slot-scope
属性(旧语法)或者v-slot
指令(新语法,v2.6+)拿到这些值。
子组件是一个单纯展示加法的组件
<template>
<div class="my-add">
<span v-for="(num, i) in nums" :key="i">
{{ num }}
<span class="add-symbol" v-if="i < nums.length - 1">+</span>
</span>
<span class="equal-symbol">= </span>
<slot :result="result">{{ result }}</slot>
</div>
</template>
<script>
export default {
name: "myAdd",
props: {
nums: {
type: Array,
default: () => [],
computed: {
result() {
return this.nums.reduce((a, b) => a + b);
</script>
父组件希望拓展子组件的功能,在加法结果大于100时显示红色告警颜色。
<template>
<div class="app-container">
<my-add v-for="(item, i) in list" :nums="item" :key="i">
<span slot-scope="{result}" :class="result > 100 ?'danger':'' ">{{result}}</span>
</my-add>
</div>
<my-add v-for="(item, i) in list" :nums="item" :key="i">
<template v-slot:default="{result}">
<span :class="result > 100 ?'danger':'' ">{{result}}</span>
</template>
</my-add>
</div>
</div>
</template>
<script>
import myAdd from "./add.vue";
export default {
name: "App",
components: {
myAdd,
data() {
return {
list: [
[1, 2, 3, 4, 5, 6],
[10, 20, 30, 40, 50, 60],
</script>
<style lang="css">
.danger {
color: #f00;
</style>
显示效果:
如果传值的是具名插槽,则新语法表示为v-slot:插槽名称="slotProps"
,其中slotProps
可以解构为具体的prop
属性。
在render函数中使用插槽
还是以上文中的按钮组件为例,转换为render函数的写法如下
render() {
return <button class="my-button2" on={this.$listeners} {...this.$attrs}>
{this.$slots.default}
</button>;
如果是函数式组件,应该这样写
render(_, {slots, listeners, data}) {
return <button class="my-button2" on={listeners} {...data}>
{slots().default}
</button>;