Appearance
✨ 插槽的本质 👌
要点速览
- 插槽集合在编译后本质是“以插槽名为键,值为函数的对象”,父组件以“具名函数”形式传递模板内容给子组件。
- 默认插槽、具名插槽、作用域插槽,最终都以“函数返回 VNode(或 VNode 数组)”呈现。
- 子组件通过调用
slots.xxx(props)取得要渲染的虚拟 DOM;作用域插槽的props由子组件提供。
概念回顾
- 子组件:通过
slot(或在 SFC 模板中的<slot/>)设置插槽位。 - 父组件:使用子组件时,将模板内容以“插槽函数”的形式传递给子组件。
使用层面的本质
插槽是“父向子传递模板内容”的机制,但在运行期,传递的是“具名函数”,调用后返回要渲染的 VNode 结构。
父组件传递内容的本质
父组件传的是一个“以插槽名为键的函数对象”:
jsx
{
default: function(){ /* 返回默认插槽的 VNode */ },
header: function(){ /* 返回具名插槽 header 的 VNode */ },
// ...
}例如:
jsx
{
default: function(){
// 注意:返回的是虚拟 DOM(可为数组)
return (
<div class="card-content">
<img src="./assets/landscape.jpeg" alt="Beautiful landscape" class="card-image" />
<p>探索未知的自然风光,记录下每一个令人惊叹的瞬间。加入我们的旅程,一起见证世界的壮丽。</p>
</div>
)
},
header: function(){
return (<div>摄影作品</div>)
}
}常见误区(父侧)
- 以为传的是“DOM 片段”,其实是“函数”;需要调用才能得到 VNode。
- 忘记具名:具名插槽需与子侧插槽名一致,否则子组件取不到对应函数。
子组件渲染插槽的本质
子组件拿到父传的 slots 对象,调用其中的函数以获得要渲染的 VNode:
jsx
const slots = {
default: function () {
/* ... */
},
header: function () {
/* ... */
},
};
// 该对象由父组件传入(在 SFC 中通过 setup 的第二个参数使用)
slots.default(); // 得到默认插槽的 VNode
slots.header(); // 得到 header 插槽的 VNode对于“作用域插槽”,子组件向插槽函数传递 props:
jsx
slots.header({ title: "子组件提供的标题" });常见误区(子侧)
slots.default可能不存在,需要判空再调用。- 插槽函数可能返回数组或空数组,需要做好结果形态的兼容处理。
- 作用域插槽的数据应由子组件提供,父组件在插槽函数体内使用这些
props。
验证示例
下面的示例展示了子组件如何调用默认插槽与具名插槽(含作用域插槽):
子组件
vue
<script>
import { defineComponent, h, ref } from "vue";
import styles from "./CardComponent.module.css";
export default defineComponent({
name: "CardComponent",
setup(_, { slots }) {
// 通过setup函数拿到slots对象,它是一个以插槽名为键的函数对象,每个函数返回要渲染的VNode(或VNode数组)
const title = ref("这是子组件标题");
// 调用默认插槽函数,若不存在则返回空数组
const defaultSlotsVNode = slots.default?.() ?? []; // 使用可选操作符和空数组合并运算符,避免null或undefined
let headerSlotVNode = null;
if (slots.header) {
// 调用具名插槽函数header,若不存在则返回null
headerSlotVNode = slots.header({ title: title.value });
}
if (
!headerSlotVNode ||
(Array.isArray(headerSlotVNode) && headerSlotVNode.length === 0)
) {
headerSlotVNode = h("div", null, "默认标题");
}
return () =>
h("div", { class: styles.card }, [
h("div", { class: styles["card-header"] }, headerSlotVNode),
h("div", { class: styles["card-body"] }, defaultSlotsVNode),
]);
},
});
</script>父组件
vue
<script>
import { defineComponent, h } from "vue";
import CardComponent from "./CardComponent";
export default defineComponent({
name: "ParentComponent",
setup() {
// setup 可以返回一个渲染函数,用于直接生成该组件的 VNode 树并填充插槽内容。
return () =>
// 渲染CardComponent组件,并且传出header插槽和默认插槽
h(CardComponent, null, {
header: ({ title }) => h("h3", null, title),
default: () => [h("p", null, "这里是默认插槽的内容")],
});
},
});
</script>说明
slots.default?.()使用可选链,避免未传默认插槽时报错。- 作用域插槽通过参数向父组件传递数据(此示例为
title)。 - 返回结果可能为空或数组,需做好兜底与形态兼容。
小结
总结
- 插槽集合在运行期时是“以插槽名为键,值为函数的对象”,父侧提供函数,子侧调用得到 VNode。
- 作用域插槽由子组件提供数据
props,父组件在插槽函数体内使用。 - 判空与结果形态兼容是插槽渲染常见的工程化要点。
