文章目录
总结
- 通过
slot(插槽)可实现"父组件向子组件传递模板内容"的功能- 子组件使用
<slot>来占位,称为"插槽出口"(slot outlet) - 父组件提供"插槽内容"(slot content)
- 子组件使用
- 插槽内容可以访问到父组件的数据作用域,但无法访问子组件的数据
- 子组件可以为插槽指定默认内容。在父组件没有指定插槽内容时,默认内容会被渲染
- 子组件可包含多个插槽,用
name属性来区分不同插槽- 没有提供
name的插槽会隐式地命名为default
- 没有提供
- 在父组件里,可通过
v-slot指令指定插槽v-slot可以简写为#
- 默认插槽和命名插槽可以混合使用,此时所有位于顶级的非
<template>节点都被隐式地视为默认插槽的内容 - 在子组件里,可以通过
$slots来获取插槽内容- 常见的操作是判断父组件是否为某个插槽提供了内容
- 通过作用域插槽,父组件可以获取子组件的数据
- 子组件:在
<slot>里通过v-bind绑定数据 - 父组件:通过
v-slot属性获取这些数据
- 子组件:在
- 无渲染组件:组件只包括了逻辑,而没有渲染内容,把数据通过作用域插槽传递给父组件
环境
- Ubuntu 24.04
- Chrome Version 146.0.7680.164 (Official Build) (64-bit)
- VSCode 1.109.5
- npm 11.6.2
- Vue 3.5.32
准备
创建一个Vue项目。创建过程略,具体可参见 https://blog.csdn.net/duke_ding2/article/details/159510007 。
关于如何导入和注册组件,参见 https://blog.csdn.net/duke_ding2/article/details/159472143 。
关于props,参见 https://blog.csdn.net/duke_ding2/article/details/159696940 。
关于emit,参见 https://blog.csdn.net/duke_ding2/article/details/159779784 。
关于v-model,参见 https://blog.csdn.net/duke_ding2/article/details/159829571 。
背景
在前面的文档中,我们了解了父子组件之间的一些通信方式:
- 父组件如何通过自定义属性向子组件传送数据
- 父组件如何通过自定义事件接收子组件的事件
- 父组件如何通过
v-model实现和子组件数据的双向绑定(本质是props和emit的组合)
现在,一个新的需求是,父组件在使用子组件时,想要传送一些模板片段,嵌入到子组件里面。
slot
通过Vue的 slot (插槽),可实现"父组件向子组件传递模板内容"的功能。
创建 MyComponent.vue 文件如下:
html
<template>
<div>
<div>开始</div>
<slot/>
<div>结束</div>
</div>
</template>
可见,在子组件里,定义了一部分模板内容,另一部分则使用 <slot> 来占位,称为"插槽出口"(slot outlet),这部分的内容称为"插槽内容"(slot content),是由父组件来定义的。
修改 App.vue 如下:
html
<template>
<div>
<MyComponent>
<h2>标题</h2>
<div style="color: blue;font-size: 20px;">内容......</div>
</MyComponent>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
注意这段代码:
html
<MyComponent>
<h2>标题</h2>
<div style="color: blue;font-size: 20px;">内容......</div>
</MyComponent>
父组件在使用子组件时,传送了一些模板内容。这些内容将会被渲染在子组件的插槽出口处。
效果如下:

可见,父组件和子组件的模板内容合并显示出来了。最终渲染的效果相当于:
html
<div>
<div>开始</div>
<h2>标题</h2>
<div style="color: blue;font-size: 20px;">内容......</div>
<div>结束</div>
</div>
作用域
因为插槽内容本身是在父组件模板中定义的,所以插槽内容可以访问到父组件的数据作用域,但无法访问子组件的数据。
也就是说:父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
默认内容
在父组件没有指定插槽内容时,子组件可以为插槽指定默认内容。
修改 MyComponent.vue 如下:
html
<template>
<div>
<div>开始</div>
<slot>这是默认内容</slot> <!-- 声明插槽时,可提供默认内容-->
<div>结束</div>
</div>
</template>
修改 App.vue 如下:
html
<template>
<div>
<MyComponent/> <!-- 不提供插槽内容-->
<br />
<MyComponent>这是自定义内容</MyComponent> <!-- 提供插槽内容-->
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
效果如下:

可见:
- 若父组件提供了插槽内容,则显示父组件提供的内容
- 若父组件不提供插槽内容,则显示子组件的默认插槽内容
命名插槽
上面的例子里,子组件都只有一个插槽。如果子组件有多个插槽,则需要通过 name 属性来区分不同的插槽。
注:没有提供 name 的插槽会隐式地命名为 default 。
修改 MyComponent.vue 如下:
html
<template>
<div>
<div>标题开始</div>
<slot name="title"/> <!-- 命名插槽 -->
<div>标题结束</div>
<br />
<div>内容开始</div>
<slot name="content"/> <!-- 命名插槽 -->
<div>内容结束</div>
</div>
</template>
可见,子组件声明了两个插槽,分别对应标题和内容。
修改 App.vue 如下:
html
<template>
<MyComponent>
<template v-slot:title>这是标题</template>
<template v-slot:content>这是内容</template>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
可见,如果子组件里有多个插槽,则在父组件里,需要通过 v-slot 指令指定插槽。
v-slot 可以简写为 # 。
默认插槽和命名插槽可以混合使用。此时所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。
修改 MyComponent.vue 如下:
html
<template>
<div>
<div>标题开始</div>
<slot name="title"/> <!-- 命名插槽 -->
<div>标题结束</div>
<br />
<div>内容开始</div>
<slot name="content"/> <!-- 命名插槽 -->
<div>内容结束</div>
<br />
<div>remark开始</div>
<slot /> <!-- 默认插槽 -->
<div>remark结束</div>
</div>
</template>
修改 App.vue 如下:
html
<template>
<MyComponent>
<template v-slot:title>这是标题</template>
<template v-slot:content>这是内容</template>
这是remark
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
效果如下:

条件插槽
在子组件里,可以通过 $slots 来获取插槽内容。常见的操作是判断父组件是否为某个插槽提供了内容。
修改 MyComponents.vue 如下:
html
<template>
<div>
<div v-if="$slots.title">
<div>标题开始</div>
<slot name="title"/> <!-- 命名插槽 -->
<div>标题结束</div>
</div>
<div v-if="$slots.content">
<br />
<div>内容开始</div>
<slot name="content"/> <!-- 命名插槽 -->
<div>内容结束</div>
</div>
<div v-if="$slots.default">
<br />
<div>remark开始</div>
<slot /> <!-- 默认插槽 -->
<div>remark结束</div>
</div>
</div>
</template>
修改 App.vue 如下:
html
<template>
<MyComponent>
<template v-slot:title>这是标题</template>
这是remark
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
效果如下:

作用域插槽
前面提到:插槽的内容无法访问到子组件的状态。
实际上,有办法可以把子组件的数据传递到父组件。具体方法为:
- 子组件:在
<slot>里通过v-bind绑定数据 - 父组件:通过
v-slot属性获取这些数据
默认插槽
修改 MyComponent.vue 如下:
html
<template>
<div>
<slot :title="titleValue" :content="contentValue"/> <!-- title 和 content 是自定义属性 -->
</div>
</template>
<script>
export default {
data() {
return {
titleValue: '这是子组件定义的标题',
contentValue: '这是子组件定义的内容',
}
}
}
</script>
修改 App.vue 如下:
html
<template>
<MyComponent v-slot="slotProps"> <!-- v-slot 属性的值可任意命名,代表包含所有自定义属性的对象 -->
<div>{{ slotProps.title }}</div>
<div>{{ slotProps.content }}</div>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
效果如下:

命名插槽
对于命名插槽,方法和默认插槽类似,唯一不同在于,父组件需要为 v-slot 属性指定 name 参数。
修改 MyComponent.vue 如下:
html
<template>
<div>
<slot name="myslot1" :title="titleValue1" :content="contentValue1"/>
<slot name="myslot2" :title="titleValue2" :content="contentValue2"/>
</div>
</template>
<script>
export default {
data() {
return {
titleValue1: '这是子组件定义的标题1',
contentValue1: '这是子组件定义的内容1',
titleValue2: '这是子组件定义的标题2',
contentValue2: '这是子组件定义的内容2',
}
}
}
</script>
注意:两个插槽都有 title 和 content 属性,但它们之间相互独立。
修改 App.vue 如下:
html
<template>
<MyComponent>
<template #myslot1="slotProps1">
<div>{{ slotProps1.title }}</div>
<div>{{ slotProps1.content }}</div>
</template>
<template #myslot2="slotProps2">
<br />
<div>{{ slotProps2.title }}</div>
<div>{{ slotProps2.content }}</div>
</template>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
可见,父组件中,对于不同的插槽,只需指定不同的对象(可任意命名)来接收数据。
效果如下:

多插槽中的默认插槽
前面提到,默认插槽会被显式命名为 default ,因此,在多插槽中,可用 default 来指定默认插槽。
修改 MyComponent.vue 如下:
html
<template>
<div>
<slot name="myslot1" :title="titleValue1" :content="contentValue1"/>
<slot name="myslot2" :title="titleValue2" :content="contentValue2"/>
<slot :title="titleValue3" :content="contentValue3"/>
</div>
</template>
<script>
export default {
data() {
return {
titleValue1: '这是子组件定义的标题1',
contentValue1: '这是子组件定义的内容1',
titleValue2: '这是子组件定义的标题2',
contentValue2: '这是子组件定义的内容2',
titleValue3: '这是子组件定义的标题3',
contentValue3: '这是子组件定义的内容3',
}
}
}
</script>
本例中,有两个命名插槽和一个默认插槽。
修改 App.vue 如下:
html
<template>
<MyComponent>
<template #myslot1="slotProps1">
<div>{{ slotProps1.title }}</div>
<div>{{ slotProps1.content }}</div>
</template>
<template #myslot2="slotProps2">
<br />
<div>{{ slotProps2.title }}</div>
<div>{{ slotProps2.content }}</div>
</template>
<template #default="slotProps3">
<br />
<div>{{ slotProps3.title }}</div>
<div>{{ slotProps3.content }}</div>
</template>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
},
}
</script>
可见,由于有多个插槽,因此在指定默认插槽时,必须显式指定其名字 default 。
效果如下:

无渲染组件
一些组件可能只包括了逻辑,而没有渲染内容,把数据通过作用域插槽传递给父组件。这种类型的组件称为无渲染组件。
比如:
html
<template>
<slot :title="titleValue" :content="contentValue"/>
</template>
<script>
export default {
data() {
return {
titleValue: '这是子组件定义的标题',
contentValue: '这是子组件定义的内容',
}
}
}
</script>
该组件没有任何渲染,只负责数据和逻辑。
参考
https://cn.vuejs.org/guide/essentials/component-basics.htmlhttps://cn.vuejs.org/guide/components/slots