一文读懂 Vue3 插槽到底是什么?

AI 概述
编译后的父组件编译后的子组件总结 插槽,给人以神秘感,尤其是作用域插槽,使我们能够直接在父组件中访问子组件的数据,进一步增加了插槽的神秘色彩。实际上,Vue3 的插槽远比你想象的简单。在本文中,我们将揭开插槽的神秘面纱。 我们先来看个常见的插槽 demo,其中子组件代码如下: <template>...
目录
文章目录隐藏
  1. 编译后的父组件
  2. 编译后的子组件
  3. 总结

插槽,给人以神秘感,尤其是作用域插槽,使我们能够直接在父组件中访问子组件的数据,进一步增加了插槽的神秘色彩。实际上,Vue3 的插槽远比你想象的简单。在本文中,我们将揭开插槽的神秘面纱。

我们先来看个常见的插槽 demo,其中子组件代码如下:

<template>
  <slot></slot>
  <slot name="header"></slot>
  <slot name="footer" :desc="desc"></slot>
</template>

<script setup>
import { ref } from "vue";
const desc = ref("footer desc");
</script>

在子组件中我们定义了三个插槽,第一个是默认插槽,第二个是 name 为header的插槽,第三个是 name 为footer的插槽,并且将desc变量传递给了父组件。

父组件代码如下:

<template>
  <ChildDemo>
    <p>default slot</p>
    <template v-slot:header>
      <p>header slot</p>
    </template>
    <template v-slot:footer="{ desc }">
      <p>footer slot: {{ desc }}</p>
    </template>
  </ChildDemo>
</template>

<script setup lang="ts">
import ChildDemo from "./child.vue";
</script>

在父组件中的代码很常规,分别使用v-slot指令给headerfooter插槽传递内容。

编译后的父组件

我们在浏览器中来看看编译后的父组件代码,简化后如下:

import {
  createBlock as _createBlock,
  createElementVNode as _createElementVNode,
  openBlock as _openBlock,
  toDisplayString as _toDisplayString,
  withCtx as _withCtx,
} from"/node_modules/.vite/deps/vue.js?v=64ab5d5e";

const _sfc_main = /* @__PURE__ */ _defineComponent({
// ...省略
});

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
    _openBlock(),
    _createBlock($setup["ChildDemo"], null, {
      header: _withCtx(
        () =>
          _cache[0] ||
          (_cache[0] = [
            _createElementVNode(
              "p",
              null,
              "header slot",
              -1
              /* HOISTED */
            ),
          ])
      ),
      footer: _withCtx(({ desc }) => [
        _createElementVNode(
          "p",
          null,
          "footer slot: " + _toDisplayString(desc),
          1
          /* TEXT */
        ),
      ]),
      default: _withCtx(() => [
        _cache[1] ||
          (_cache[1] = _createElementVNode(
            "p",
            null,
            "default slot",
            -1
            /* HOISTED */
          )),
      ]),
      _: 1,
      /* STABLE */
    })
  );
}
exportdefault/* @__PURE__ */ _export_sfc(_sfc_main, [
  ["render", _sfc_render],
]);

从上面的代码可以看到 template 中的代码编译后变成了 render 函数。

在 render 函数中_createBlock($setup["ChildDemo"]表示在渲染子组件ChildDemo,并且在执行createBlock函数时传入了第三个参数是一个对象。对象中包含headerfooterdefault三个方法,这三个方法对应的是子组件ChildDemo`中的三个插槽。执行这三个方法就会生成这三个插槽对应的虚拟 DOM。

并且我们观察到插槽footer处的方法还接收一个对象作为参数,并且对象中还有一个desc字段,这个字段就是子组件传递给父组件的变量。

方法最外层的withCtx方法是为了给插槽的方法注入当前组件实例的上下文。

通过上面的分析我们可以得出一个结论:在父组件中插槽经过编译后会变成一堆由插槽 name 组成的方法,执行这些方法就会生成插槽对应的虚拟 DOM。默认插槽就是 default 方法,方法接收的参数就是子组件中插槽给父组件传递的变量。但是有一点要注意,在父组件中我们只是定义了这三个方法,执行这三个方法的地方却不是在父组件,而是在子组件。

编译后的子组件

我们来看看编译后的子组件,简化后代码如下:

import {
  createElementBlock as _createElementBlock,
  Fragment as _Fragment,
  openBlock as _openBlock,
  renderSlot as _renderSlot,
} from"/node_modules/.vite/deps/vue.js?v=64ab5d5e";

const _sfc_main = {
// ...省略
};

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
    _openBlock(),
    _createElementBlock(
      _Fragment,
      null,
      [
        _renderSlot(_ctx.$slots, "default"),
        _renderSlot(_ctx.$slots, "header"),
        _renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),
      ],
      64/* STABLE_FRAGMENT */
    )
  );
}

exportdefault/*#__PURE__*/ _export_sfc(_sfc_main, [["render", _sfc_render]]);

同样的我们观察里面的 render 函数,里面的这个:

[
  _renderSlot(_ctx.$slots, "default"),
  _renderSlot(_ctx.$slots, "header"),
  _renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),
]

对应的就是源代码里面的这个:

<slot></slot>
<slot name="header"></slot>
<slot name="footer" :desc="desc"></slot>

在上面我们看见一个$slots对象,这个是什么东西呢?

其实useSlots就是返回的$slots对象,我们直接在控制台中使用useSlots打印出$slots对象看看,代码如下:

<script setup>
import { ref, useSlots } from "vue";

const slots = useSlots();
console.log(slots);
const desc = ref("footer desc");
</script>

我们来浏览器中看看此时打印的slots对象是什么样的,如下图:

打印的 slots 对象

从上图中可以看到 slots 对象好像有点熟悉,这个对象中包含defaultfooterheader这三个方法,其实这个 slots 对象就是前面我们讲的父组件中定义的那个对象,执行对象的defaultfooterheader方法就会生成对应插槽的虚拟 DOM。

前面我们讲了在父组件中会定义defaultfooterheader这三个方法,那这三个方法又是在哪里执行的呢?

答案是:在子组件里面执行的。

在执行_renderSlot(_ctx.$slots, "default")方法时就会去执行slots对象里面的default方法,这个是renderSlot函数的代码截图:

renderSlot 函数的代码

从上图中可以看到在renderSlot函数中首先会使用slots[name]拿到对应的插槽方法,如果执行的是_renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),这里拿到的就是footer方法。

然后就是执行footer方法,前面我们讲过了这里的footer方法需要接收参数,并且从参数中结构出desc属性。刚好我们执行renderSlot方法时就给他传了一个对象,对象中就有一个desc属性,这不就对上了吗!

并且由于执行footer方法会生成虚拟 DOM,所以 footer 生成的虚拟 DOM 是属于子组件里面的,同理 footer 对应的真实 DOM 也是属于在子组件的 DOM 树里面。

通过上面的分析我们可以得出一个结论就是:子组件中的插槽实际就是在执行父组件插槽对应的方法,在执行方法时可以将子组件的变量传递给父组件,这就是作用域插槽的原理。

总结

这篇文章我们讲了经过编译后父组件的插槽会被编译成一堆方法,这些方法组成的对象就是$slots对象。在子组件中会去执行这些方法,并且可以将子组件的变量传给父组件,由父组件去接收参数,这就是作用域插槽的原理。

以上关于一文读懂 Vue3 插槽到底是什么?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 一文读懂 Vue3 插槽到底是什么?

发表回复