3分钟,了解一下Vue3中的插槽到底是个啥

插槽看着是一个比较神秘的东西,特别是作用域插槽还能让我们在父组件里面直接访问子组件里面的数据,这让插槽变得更加神秘了。其实Vue3的插槽远比你想象的简单,这篇文章我们来揭开插槽的神秘面纱。

看个demo

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

xml 复制代码
<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变量传递给了父组件。

我们再来看看父组件代码如下:

xml 复制代码
<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插槽传递内容。

来看看编译后的父组件

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

javascript 复制代码
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 */
    })
  );
}
export default /* @__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方法,方法接收的参数就是子组件中插槽给父组件传递的变量。但是有一点要注意,在父组件中我们只是定义了这三个方法,执行这三个方法的地方却不是在父组件,而是在子组件。

编译后的子组件

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

php 复制代码
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 */
    )
  );
}

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

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

php 复制代码
[
  _renderSlot(_ctx.$slots, "default"),
  _renderSlot(_ctx.$slots, "header"),
  _renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),
]

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

ruby 复制代码
<slot></slot>
<slot name="header"></slot>
<slot name="footer" :desc="desc"></slot>

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

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

xml 复制代码
<script setup>
import { ref, useSlots } from "vue";

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

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

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

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

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

在执行_renderSlot(_ctx.$slots, "default")方法时就会去执行slots对象里面的default方法,这个是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对象。在子组件中会去执行这些方法,并且可以将子组件的变量传给父组件,由父组件去接收参数,这就是作用域插槽的原理。了解了这个后当我们在useSlotsjsxtsx中定义和使用插槽就不会那么迷茫了。

转载出处:www.cnblogs.com/heavenYJJ/p...

行业拓展

分享一个面向研发人群使用的前后端分离的低代码软件------JNPF,适配国产化,支持主流数据库和操作系统。

提供五十几种高频预制组件,包括表格、图表、列表、容器、表单等,内置常用的后台管理系统使用场景和基本需求,配置了流程引擎、表单引擎、报表引擎、图表引擎、接口引擎、门户引擎、组织用户引擎等可视化功能引擎,超过数百种功能控件以及大量实用模板,使得在拖拉拽的简单操作下,也能完成开发。

对于工程师来说,灵活的使用高质量预制组件可以极大的节省时间,将更多精力花费在更有创造性和建设性的代码上。

相关推荐
一枚小小程序员哈3 小时前
基于Vue + Node能源采购系统的设计与实现/基于express的能源管理系统#node.js
vue.js·node.js·express
一枚小小程序员哈7 小时前
基于Vue的个人博客网站的设计与实现/基于node.js的博客系统的设计与实现#express框架、vscode
vue.js·node.js·express
定栓7 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
LIUENG8 小时前
Vue3 响应式原理
前端·vue.js
wycode9 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
wycode10 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏10 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
pepedd86411 小时前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
前端缘梦11 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试
HWL567911 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js