Vue render是Vue在编译模板时的必经之路,通过 template 形式写出的模板最终会经由 render 函数渲染到dom上,正如 render 介绍所说的
然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器
如果你还不了解 render 函数的作用以及能力,建议你现在官网了解一下 :)
Vue官网 render函数介绍
如果你还不了解 slot 的作用以及能力,建议你现在官网了解一下 :)
Vue官网 插槽、具名插槽、作用域插槽介绍
slot(插槽)是 父组件 向 子组件 提供内容的一种方式,下面贴出代码
// 父组件使用 child 组件
<child>child>
// 子组件内容
<template>
<div>
我是子组件!
<slot>我是默认插槽的后备内容slot>
<br>
<slot name="content">我是具名插槽 content 的后备内容slot>
<br>
<slot name="other">我是具名插槽 other 的后备内容slot>
div>
template>
这样对应的结果是
我是子组件! 我是默认插槽的后备内容
我是具名插槽 content 的后备内容
我是具名插槽 other 的后备内容
我们先来看看子组件对应的 $slots ==> {}
是一个空对象,这是由于 现在父组件部分还没有为子组件提供插槽的内容
如果父组件形如
<child>
<template v-slot:default>
我是父组件提供的默认内容
template>
<template v-slot:content>
我是父组件提供的 content
template>
<template v-slot:other>
我是父组件提供的 other
template>
child>
则渲染结果为
我是子组件!
我是父组件提供的默认内容
我是父组件提供的 content
我是父组件提供的 other
子组件对应的 $slot为
{
content:
[VNode {
tag: undefined, data: undefined, children: undefined, text: " 我是父组件提供的 content ", elm: undefined, …}]
default:
[VNode {
tag: undefined, data: undefined, children: undefined, text: " 我是父组件提供的默认插槽 ", elm: undefined, …}]
other:
[VNode {
tag: undefined, data: undefined, children: undefined, text: " 我是父组件提供的 other ", elm: undefined, …}]
}
可以看到, 子组件中的 $slot 将父组件提供的内容传了过来,
$slot 对象中的每个属性对应一个具名插槽(slot的名称为 default),它的内容则是父组件提供的模板经由编译之后的 VNode 对象,这个VNode对象后续会渲染为对应的内容
注意! 父组件再没有书写 v-slot 时子组件的 $slots 为空!因此再编写子组件渲染函数的时候注意检查
同样使用上面的组件,这次稍微修改些内容
<template>
<div>
我是子组件!
<br>
<slot text="我是默认插槽">我是默认插槽的后备内容slot>
<br>
<slot name="content" text="我是具名插槽content">我是具名插槽 content 的后备内容slot>
<br>
<slot name="other" text="我是具名插槽other">我是具名插槽 other 的后备内容slot>
div>
template>
父组件通过作用域插槽获取到子组件暴露的内容
<child>
<template v-slot:default="prop">
{
{ prop }}
template>
<template v-slot:content="prop">
{
{ prop }}
template>
<template v-slot:other="prop">
{
{ prop }}
template>
child>
这是的效果是
我是子组件!
{
"text": "我是默认插槽" }
{
"text": "我是具名插槽content" }
{
"text": "我是具名插槽other" }
子组件中的 $scopedSlots 的内容是
{
content: ƒ ()
default: ƒ ()
other: ƒ ()
$hasNormal: false
$key: undefined
$stable: true
}
可以看到,这个对象中出现了和具名插槽名称一致的三个函数 content default 和 other
这三个函数可以接收一个参数作为要给父组件使用的对象,并返回[VNode]
换句话说,v-slot:content="prop"
中的 prop 就是 $scopedSlots.content(prop)
这个参数给的
注意! 父组件再没有书写 v-slot 时子组件的 $scopedSlots 没有上述函数!因此再编写子组件渲染函数的时候注意检查
在 Vue API 中了解更多
有了上面内容的铺垫,可以看到,不论是 $slots 还是 $scopedSlots ,它们的属性都是父组件向子组件注入的内容决定的,只不过 $scopedSlots可以再向父组件抛出数据
它们是在模板上编写
现在我们在 render 上自己执行它们
就第一个 slots 的例子来改,可以改为
<template>
<div>
我是子组件!
<slot>我是默认插槽的后备内容</slot>
<br>
<slot name="content">我是具名插槽 content 的后备内容</slot>
<br>
<slot name="other">我是具名插槽 other 的后备内容</slot>
</div>
</template>
--------------------------------------------变为了-------------------------------------------
render(_c) {
let def = this.$slots.default;
def || (def = "我是默认插槽的后备内容");
let con = this.$slots.content;
con || (con = "我是具名插槽 content 的后备内容");
let oth = this.$slots.other;
oth || (oth = "我是具名插槽 other 的后备内容");
return _c("div", [
"我是子组件!",
_c("br"),
def,
_c("br"),
con,
_c("br"),
oth,
]);
},
父组件提供内容、父组件不提供内容的效果依次为:
我是子组件!
我是父组件提供的默认内容
我是父组件提供的 content
我是父组件提供的 other
我是子组件!
我是默认插槽的后备内容
我是具名插槽 content 的后备内容
我是具名插槽 other 的后备内容
对于 $scopedSlots,使用起来也大同小异
<template>
<div>
我是子组件!
<br>
<slot text="我是默认插槽"></slot>
<br>
<slot name="content" text="我是具名插槽content"></slot>
<br>
<slot name="other" text="我是具名插槽other"></slot>
</div>
</template>
--------------------------------------------变为了-------------------------------------------
---------------注意! 示例没有进行类型检查,但是开发时无法保证所有的插槽正确、完整书写,---------
---------------需要类型检查或提供默认值,见下方注释--------------------------------------------
render(_c) {
// 如果要提供后备内容可以通过 判断 this.$scopedSlots.xxx 不为函数后渲染默认内容等方式
// 这里省略
let def = this.$scopedSlots.default({
text: "我是默认插槽",
});
let con = this.$scopedSlots.content({
text: "我是具名插槽content",
});
let oth = this.$scopedSlots.other({
text: "我是具名插槽other",
});
return _c("div", [
"我是子组件!",
_c("br"),
def,
_c("br"),
con,
_c("br"),
oth,
]);
},
效果和 $scopedSlots 部分的结果一致
我是子组件!
{
"text": "我是默认插槽" }
{
"text": "我是具名插槽content" }
{
"text": "我是具名插槽other" }
它不同于 $scopedSlots,它是存在于 render数据对象 中的一个对象
效果是获取 子组件向父组件 抛出的内容而不是抛出内容给父组件
就以刚刚的父组件为例!
<child>
<template v-slot:default="prop">
{
{
prop }}
</template>
<template v-slot:content="prop">
{
{
prop }}
</template>
<template v-slot:other="prop">
{
{
prop }}
</template>
</child>
--------------------------------------------变为了-------------------------------------------
---------------注意! 示例没有进行类型检查,但是开发时无法保证所有的插槽正确、完整书写,-----------
render(_c) {
return _c("child", {
scopedSlots: {
default(prop) {
// 对应 v-slot:default="prop"
return prop;
},
content(prop) {
// 对应 v-slot:default="prop"
return prop;
},
other(prop) {
// 对应 v-slot:default="prop"
return prop;
},
},
});
},
结果一致:
我是子组件!
{
"text": "我是默认插槽" }
{
"text": "我是具名插槽content" }
{
"text": "我是具名插槽other" }
简单来说:
v-slot:name
====> 子组件 this.$slots.name
v-slot
或者单纯的
标签 ====> 子组件 this.$slots.default
v-slot="xxx"
====> 子组件抛出this.$scopedSlots.default(xxx)
/ 父组件使用scoepdSlots: { default(xxx){ return VNode } }
v-slot:name="xxx"
====> 子组件抛出this.$scopedSlots.name(xxx)
/ 父组件使用scoepdSlots: { name(xxx){ return VNode } }
实践是检验真理的唯一标准,如果你了解 element-ui 的话,可以到我的 github 上查看一个以面向对象的形式创建简单表格的工具,在源码中有使用这三者的部分。⬇
小工具