Skip to content

Vue.js 插槽的本质

Published:

前言

在 Vue.js 中,插槽(slot)是一个非常重要的概念,它让我们可以在组件之间实现内容分发。虽然 Vue.js 的官方文档对插槽进行了详细的介绍,但初次接触时可能会觉得有点复杂,什么默认插槽、具名插槽、作用域插槽、动态插槽等。

一个例子

假如我们现在有一个组件,组件中有多个插槽:

<!-- Child.vue -->
<template>
  <div>
    <slot />
    <slot name="slot1" />
    <slot name="slot2" msg="Hello, world" />
  </div>
</template>

那么,我们在父组件中导入子组件并使用插槽:

<!-- Parent.vue -->
<VueSlot>
  <p>Default Slot</p>
  <template #slot1>
    <p>Slot 1</p>
  </template>
  <template #slot2="{ msg }">
    <p>Slot 2: {{ msg }}</p>
  </template>
</VueSlot>

很多人可能会以为 slot 就是简单的占位符,用来在父组件和子组件之间传递内容。但实际上,在父组件中使用插槽传递内容给子组件的过程,是将一个对象传递给子组件。

如果我们用 JSX 语法来重写上面的例子,它的本质会变得更加明显:

{
  default: () => <p>Default Slot</p>,
  slot1: () => <p>Slot 1</p>,
  slot2: (props) => <p>Slot 2: {props.msg}</p>
}

上面的代码表示,父组件实际上将一个对象传递给了子组件,键是插槽的名称,值是返回虚拟节点的函数。

Vue.js 的虚拟节点创建

为了更深入地理解插槽,我们可以借助 Vue.js 的 createElementVNode 函数来手动创建虚拟节点。这可以帮助我们看到插槽内容如何在底层被处理。javascript

export default {
  setup() {
    return () => {
      return createElementVNode("div", null, []);
    };
  },
};

当我们使用 createElementVNode 创建虚拟节点时,我们实际上是在手动构建 Vue.js 内部的 VNode 结构,这与插槽传递的内容形式是类似的。

·

在 JavaScript 中操作插槽

既然插槽是以对象的形式传递的,我们自然可以在子组件中访问这个对象。下面我们来看一下如何在 setup 函数中访问插槽对象:

export default {
  setup(props, { slots }) {
    console.log(slots);
    return () => {
      return createElementVNode("div", null, []);
    };
  },
};

在控制台打印出来后我们会发现,与上文分析的结果是一样的。通过 slots 对象,我们可以访问传递过来的插槽内容,并且这些插槽内容以函数的形式存在。调用这些函数时,我们会得到相应的虚拟节点。

多个默认插槽

那为什么打印出来的是一个数组,因为默认的插槽我们可以传递多个虚拟节点。

<VueSlot>
  <p>Default Slot</p>
  <p>Default Slot</p>
  <p>Default Slot</p>
  <p>Default Slot</p>
  <p>Default Slot</p>
  <p>Default Slot</p>
  <template #slot1>
    <p>Slot 1</p>
  </template>
  <template #slot2="{ msg }">
    <p>Slot 2: {{ msg }}</p>
  </template>
</VueSlot>

接下来我们就可以决定这些虚拟节点可以渲染到什么位置。

setup(props, { slots }) {
  const defaultVNodes = slots.default()
  return () => {
    return createElementVNode('div', null, [
      ...defaultVNodes
    ])
  }
}

这就是插槽的本质,传递一个对象给子组件,子组件接收这个虚拟节点对象,然后自行决定渲染在哪里。