VUE render 函数中使用插槽、具名插槽与作用域插槽

1、Vue render

Vue render是Vue在编译模板时的必经之路,通过 template 形式写出的模板最终会经由 render 函数渲染到dom上,正如 render 介绍所说的

然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器

如果你还不了解 render 函数的作用以及能力,建议你现在官网了解一下 :)
Vue官网 render函数介绍

2、slots

如果你还不了解 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 为空!因此再编写子组件渲染函数的时候注意检查

3、scopedSlots

同样使用上面的组件,这次稍微修改些内容

  <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 中了解更多

4、在 render 函数中的使用 $slots/$scopedSlots

有了上面内容的铺垫,可以看到,不论是 $slots 还是 $scopedSlots ,它们的属性都是父组件向子组件注入的内容决定的,只不过 $scopedSlots可以再向父组件抛出数据

它们是在模板上编写 后 Vue 替你进行的下一步操作。
现在我们在 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" }

5、使用 scopedSlots

它不同于 $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" }

6、总结

简单来说:
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 } }

7、实践

实践是检验真理的唯一标准,如果你了解 element-ui 的话,可以到我的 github 上查看一个以面向对象的形式创建简单表格的工具,在源码中有使用这三者的部分。⬇
小工具

你可能感兴趣的:(Vue,vue)