vm.$slots和vm.$scopedSlots 在渲染函数内的使用

以下例子是在Vue@2.6.2中运行的。

vm.$slotsvm.$scopedSlots 是两个与插槽相关的属性。在使用渲染函数替代vue的模板功能时我们可能需要用到这两个属性来实现插槽的功能。

下面利用一个例子来研究 vm.$slots vm.$scopedSlots 的区别。

<!-- App.vue -->

<template>
  <div id="app">
    <HelloWorld>
      <h1 slot="head" slot-scope="{author}">this is head slot -- {{author}}</h1>
      <div>this is default slot</div>
      <div slot="foot">this is foot slot</div>
    </HelloWorld>
  </div>
</template>

<!-- 后面代码 略 -->
<!-- HelloWorld.vue -->

<template>
  <div class="hello">
    <slot name="head" author="Allen"></slot>
    <slot name="aside"></slot>  
    <slot></slot>
    <slot name="foot"></slot>  
  </div>
</template>

<!-- 后面代码 略 -->

我们在 HelloWorld.vue 内定义三个具名插槽和一个默认插槽。在 App.vue 内实例化HelloWorld组件时,只使用了 head default foot 插槽,其中 head 插槽是一个作用域插槽。

然后我们将 vm.$slots vm.$scopedSlots打印的到控制台上,chrome浏览器上打印的结果如下:

vm.$slots

vm.$slots

观察发现

  • 只有被使用到的插槽才会出现在 vm.$slots 内。
  • 默认插槽和具名插槽的值是虚拟dom数组(VNode[]),作用域插槽对应的值是一个 get 方法且属性修饰为不可枚举。
vm.$scopedSlots

vm.$scopedSlots

观察发现

  • 只有被使用到的插槽才会出现在 vm.$scopedSlots 内。
  • 这里面多了 $stable _normalized 属性。具体什么作用,未知。
  • 无论是具名插槽、默认插槽、作用域插槽都在,而且值是一个工厂函数,调用后会返回虚拟dom数组(VNode[])。
  • 作用域插槽对应的函数内多个参数 scope,可以通过它将参数传给插槽。
  • 所有值都是可枚举的。

vm.$slots在开发中的使用

以下例子来自Vue官方文档,仅做了些许的改动。

简单的例子

<!-- MyHeadline.vue -->

<script>
export default {
  name: "MyHeadline",
  props: {
    level: {
      type: Number,
      required: true
    }
  },
  render(createElement) {
    return createElement("h" + this.level, this.$slots.default);
  }
};
</script>
<!-- App.vue -->

<template>
  <div id="app">
    <MyHeadline :level="1">这是level 1</MyHeadline>
    <MyHeadline :level="2">这是level 2</MyHeadline>
    <MyHeadline :level="3">这是level 3</MyHeadline>
    <MyHeadline :level="4">这是level 4</MyHeadline>
    <MyHeadline :level="5">这是level 5</MyHeadline>
    <MyHeadline :level="6">这是level 6</MyHeadline>
  </div>
</template>

<!-- 后面代码 略 -->

页面渲染的结果:

渲染结果

页面的html结构:

html结构

MyHeadline 组件的目的很明显,就是根据传入的 level 属性生成对应级别的h标签。使用vue的模板是很难实现这样的功能的,所以这里用到了vue的渲染函数(其实vue模板最终也是编译成渲染函数)。

进阶例子

<!-- AnchoredHeading.vue -->

<script>
var getChildrenTextContent = function(children) {
  return children
    .map(function(node) {
      return node.children ? getChildrenTextContent(node.children) : node.text;
    })
    .join("");
};

export default {
  name: "AnchoredHeading",
  props: {
    level: {
      type: Number,
      required: true
    }
  },
  render(createElement) {
    // 创建 kebab-case 风格的ID
    var headingId = getChildrenTextContent(this.$slots.default)
      .toLowerCase()
      .replace(/\W+/g, "-")
      .replace(/(^\-|\-$)/g, "");

    return createElement("h" + this.level, [
      createElement(
        "a",
        {
          attrs: {
            name: headingId,
            href: "#" + headingId
          }
        },
        this.$slots.default
      )
    ]);
  }
};
</script>
<!-- App.vue -->

<template>
  <div id="app">
    <AnchoredHeading :level="1">chapter 1 love</AnchoredHeading>
    <AnchoredHeading :level="2">chapter 2 marry</AnchoredHeading>
    <AnchoredHeading :level="3">chapter 3 rival</AnchoredHeading>
    <AnchoredHeading :level="4">chapter 4 hate</AnchoredHeading>
    <AnchoredHeading :level="5">chapter 5 divorce</AnchoredHeading>
    <AnchoredHeading :level="6">chapter 6 真香</AnchoredHeading>
  </div>
</template>

<!-- 后面代码 略 -->

页面渲染结果:

渲染结果2

页面html结构:

html结构2

vm.$scopedSlots在开发中的使用

简单例子

<!-- Menu.vue -->

<script>
export default {
  name: "Menu",
  render(createElement) {
    return createElement(
      "div",
      { class: "menuBox" },
      this.$scopedSlots.default({ title: "? 菜单栏目 ?" })
    );
  }
};
</script>
<!-- MenuItem.vue -->

<script>
export default {
  name: "MenuItem",
  render(createElement) {
    return createElement("div", { class: "MenuItem" }, this.$slots.default);
  }
};
</script>
<!-- App.vue -->

<template>
  <div id="app">
    <Menu>
      <template slot-scope="{title}">
        <h4>{{title}}</h4>
        <MenuItem>首页</MenuItem>
        <MenuItem>产品</MenuItem>
        <MenuItem>公司简介</MenuItem>
        <MenuItem>联系我们</MenuItem>
      </template>
    </Menu>
  </div>
</template>

<!-- 后面代码 略 -->

Menu.vue内我们通过this.$scopedSlots.default({ title: "? 菜单栏目 ?" })向作用域插槽传了参数({ title: "? 菜单栏目 ?" })。然后我们在 App.vue 内使用 <template slot-scope="{title}"> 获得传过来的参数(这里用了解构赋值)。

上面只是为了展示vm.$scopedSlots在渲染函数内的使用,这个例子内的行为有点多此一举。

页面渲染结果:

scopedslots渲染结果.png

页面html结构:

scopedslots_html结构.png

在渲染函数内插入作用域插槽

如果我们把上面那个例子里App.vue的模板写法改成渲染函数的话,我们该如何插入作用域插槽呢?

在模板写法内插入插槽很简单,只要在组件标签内插入即可。在渲染函数内需要用到createElement方法内的scopedSlots选项。

<!-- App.vue -->

<script>
import Menu from "./components/Menu.vue";
import MenuItem from "./components/MenuItem.vue";

export default {
  name: "app",
  components: {
    Menu,
    MenuItem
  },
  render(createElement) {
    return createElement("div", { attrs: { id: "app" } }, [
      createElement("Menu", {
        scopedSlots: {
          default: ({ title }) => {
            return [
              createElement("h4", title),
              createElement("MenuItem", "首页"),
              createElement("MenuItem", "产品"),
              createElement("MenuItem", "公司简介"),
              createElement("MenuItem", "联系我们")
            ];
          }
        }
      })
    ]);
  }
};
</script>

这个写法和上面的模板写法的效果是等同的。

Logo

前往低代码交流专区

更多推荐