Vue3 在 render 函数中使用 slot
2023/9/25大约 4 分钟
工作上有一个需求:需要为一个按钮添加文本提示,我使用的是 Tooltip 组件,不过这个模块需要使用 render 函数编写,所以需要在 render 函数中使用到 slot。
在网上查到的资料大部分都不太可行或代码过于臃肿,最后在官方文档找到了解决方案。
基本概念
Vue3 在 render
函数中通过 slots
对象来访问和渲染插槽内容。slots
是一个包含所有插槽函数的对象,每个插槽函数返回的是 VNode(虚拟节点)。
在组件中定义和使用插槽
1. 默认插槽
默认插槽是最基本的插槽类型,不需要指定名称。
在 render 函数中定义默认插槽
export default {
name: "MyComponent",
render() {
const { h } = this.$createElement;
// slots.default() 调用默认插槽函数并渲染其内容
return h("div", this.$slots.default());
},
};
等价于模板语法:
<!-- 模板语法 -->
<div>
<slot />
</div>
使用组件时传递默认插槽内容
// 在父组件的 render 函数中
h(MyComponent, null, () => "默认插槽内容");
也可以传递更复杂的内容:
h(MyComponent, null, () => [
h("span", "这是"),
h("strong", "默认插槽"),
h("span", "的复杂内容"),
]);
2. 具名插槽
具名插槽允许定义多个不同名称的插槽,以便在不同位置渲染不同的内容。
在 render 函数中定义具名插槽
export default {
name: "LayoutComponent",
props: {
headerTitle: {
type: String,
default: "页面标题",
},
},
render() {
const { h } = this.$createElement;
const { props, $slots } = this;
return h("div", null, [
// 具名插槽:header
h(
"header",
$slots.header
? $slots.header({ title: props.headerTitle })
: h("h1", props.headerTitle)
),
// 默认插槽
h("main", $slots.default ? $slots.default() : h("div", "默认内容")),
// 具名插槽:footer
h("footer", $slots.footer ? $slots.footer() : h("div", "页脚")),
]);
},
};
等价于模板语法:
<!-- 模板语法 -->
<div>
<header>
<slot name="header" :title="headerTitle">
<h1>{{ headerTitle }}</h1>
</slot>
</header>
<main>
<slot>默认内容</slot>
</main>
<footer>
<slot name="footer">页脚</slot>
</footer>
</div>
使用组件时传递具名插槽内容
在 render 函数中,传递具名插槽内容需要使用对象形式,对象的键是插槽名称,值是返回 VNode 的函数:
h(
LayoutComponent,
{
props: { headerTitle: "我的页面" },
},
{
// 具名插槽:header
header: (scope) => h("h1", `自定义 ${scope.title}`),
// 默认插槽
default: () => h("p", "这是页面的主要内容"),
// 具名插槽:footer
footer: () => h("div", "© 2023 我的网站"),
}
);
作用域插槽
作用域插槽允许子组件向父组件传递数据,父组件可以根据这些数据来渲染不同的内容。
在 render 函数中定义作用域插槽
export default {
name: "ListComponent",
props: {
items: {
type: Array,
default: () => [],
},
},
render() {
const { h } = this.$createElement;
const { items, $slots } = this;
return h(
"ul",
null,
items.map((item, index) => {
// 向插槽传递数据
return h(
"li",
$slots.default
? $slots.default({ item, index })
: h("span", item.name)
);
})
);
},
};
等价于模板语法:
<!-- 模板语法 -->
<ul>
<li v-for="(item, index) in items" :key="index">
<slot :item="item" :index="index">{{ item.name }}</slot>
</li>
</ul>
使用作用域插槽
h(
ListComponent,
{
props: {
items: [
{ id: 1, name: "项目1" },
{ id: 2, name: "项目2" },
{ id: 3, name: "项目3" },
],
},
},
{
default: (scope) => {
// scope 包含了子组件传递的数据
return h("div", null, [
h("span", `#${scope.index + 1}`),
h("strong", scope.item.name),
]);
},
}
);
实际应用案例
案例:在 render 函数中使用 Tooltip 组件
下面是一个实际工作中的例子,展示如何在 render 函数中使用带有插槽的第三方组件(如 Tooltip):
export default {
name: "CustomButtonWithTooltip",
props: {
custom: {
type: Object,
default: () => ({}),
},
onCustom: {
type: Function,
default: () => {},
},
},
render() {
const { h } = this.$createElement;
const { custom, onCustom } = this;
// 假设 Ntooltip 和 NButton 是从 UI 库导入的组件
return h(Ntooltip, null, {
// trigger 是 Tooltip 组件的一个具名插槽
trigger: () =>
h(
NButton,
{
style: {
display: custom && custom.show ? "inline-flex" : "none",
},
type: "primary",
...custom,
onClick: () => onCustom(),
},
() => (custom ? custom.value : "自定义")
),
// default 是 Tooltip 组件的默认插槽(提示内容)
default: () => h("span", null, custom.tooltip),
});
},
};
案例:复杂组件嵌套和插槽传递
在更复杂的场景中,我们可能需要在多个组件之间传递插槽内容:
export default {
name: "ComplexForm",
render() {
const { h } = this.$createElement;
return h(
"div",
{
class: "complex-form",
},
[
h("h2", "复杂表单"),
// 传递插槽给 FormCard 组件
h(
FormCard,
{
title: "用户信息",
},
{
header: () =>
h("div", { class: "form-card-header" }, "用户基本信息"),
content: () => [
h("input", { placeholder: "用户名" }),
h("input", { placeholder: "邮箱" }),
h("input", { placeholder: "电话" }),
],
footer: () =>
h("button", { onClick: this.handleUserInfoSave }, "保存用户信息"),
}
),
// 另一个带插槽的 FormCard 组件
h(
FormCard,
{
title: "产品信息",
},
{
content: () => [
h("input", { placeholder: "产品名称" }),
h("input", { placeholder: "产品价格" }),
h("textarea", { placeholder: "产品描述" }),
],
footer: () =>
h(
"button",
{ onClick: this.handleProductInfoSave },
"保存产品信息"
),
}
),
]
);
},
methods: {
handleUserInfoSave() {
// 处理用户信息保存逻辑
},
handleProductInfoSave() {
// 处理产品信息保存逻辑
},
},
};
注意事项和最佳实践
检查插槽是否存在:在使用插槽前,最好检查插槽是否存在,以避免因插槽未定义而导致的错误:
h("div", this.$slots.default ? this.$slots.default() : h("div", "默认内容"));
传递参数给插槽:可以通过函数调用的方式向插槽传递参数,这些参数可以在父组件中使用:
this.$slots.header({ title: this.headerTitle, count: 5 });
使用箭头函数作为插槽内容:在传递插槽内容时,应使用箭头函数以确保正确的上下文:
h(MyComponent, null, { header: () => h("h1", "标题") });
结合组合式 API 使用:在使用组合式 API 时,可以通过
useSlots()
函数来获取 slots:import { h, useSlots } from "vue"; export default { setup(props) { const slots = useSlots(); return () => h("div", slots.default && slots.default()); }, };
更新日志
2025/9/28 09:03
查看所有更新日志
38e56
-于