前言

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

欧阳也在找工作,坐标成都求内推!

看个demo

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

编译后的子组件

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

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函数,里面的这个:

[
_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对象好像有点熟悉,这个对象中包含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中定义和使用插槽就不会那么迷茫了。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

花3分钟来了解一下Vue3中的插槽到底是什么玩意的更多相关文章

  1. 花3分钟了解下C/C++中的函数可变参简单实现

    1.可变参函数的原理 C/C++函数的参数是存放在栈区的,并且参数的入栈是从参数的右边开始,即最后一个参数先入栈,而第一个参数最后才入栈,所以,根据栈的后进先出性质,函数总能找到第一个参数.所以,可变 ...

  2. 花十分钟,让你变成AI产品经理

    花十分钟,让你变成AI产品经理 https://www.jianshu.com/p/eba6a1ca98a4 先说一下你阅读本文可以得到什么.你能得到AI的理论知识框架:你能学习到如何成为一个AI产品 ...

  3. 【MySQL】花10分钟阅读下MySQL数据库优化总结

    1.花10分钟阅读下MySQL数据库优化总结http://www.kuqin.com2.扩展阅读:数据库三范式http://www.cnblogs.com3.my.ini--->C:\Progr ...

  4. 【三分钟视频教程】iOS开发中 Xcode 报 apple-o linker 错误的#解决方案#

      [三分钟视频教程]iOS开发中 Xcode 报 apple-o linker 错误的#解决方案#   同样的道理,指向同一库文件的代码语句如果重复书写,即使重复书写所在的文件名字不同,同样会造成这 ...

  5. 【黑科技】花几分钟和孩子动手DIY,即可用手机完成全息影像!

    http://baobao.sohu.com/20160902/n467277059.shtml [黑科技]花几分钟和孩子动手DIY,即可用手机完成全息影像! 杭州亲子圈2016-09-02 07:2 ...

  6. vue3中使用axios如何去请求数据

    在vue2中一般放在created中,但是在vue3中取消了created生命周期,请求方式有两种 直接在setup中去获取数据 setup(props) { const data = reactiv ...

  7. vue3中watch函数

    watch 监听普通类型 let count = ref(1); const changeCount = () => { count.value+=1 }; watch(count, (newV ...

  8. 端午总结Vue3中computed和watch的使用

    1使用计算属性 computed 实现按钮是否禁用 我们在有些业务场景的时候,需要将按钮禁用. 这个时候,我们需要使用(disabled)属性来实现. disabled的值是true表示禁用.fals ...

  9. vue 3 学习笔记 (七)——vue3 中 computed 新用法

    vue3 中 的 computed 的使用,由于 vue3 兼容 vue2 的选项式API,所以可以直接使用 vue2的写法,这篇文章主要介绍 vue3 中 computed 的新用法,对比 vue2 ...

  10. 在vue3中使用router-link-active遇到的坑

    在使用 router-link-active 设置链接激活时CSS类名时,发现在例如 /member/order 和 /member/order/:id 这两个都包含 /member/order的路由 ...

随机推荐

  1. 自用Idea内存配置

    自用Idea内存配置 如下: 使用了zgc,自用48g内存的mac.可以应对8后端4前端同时使用. -Xms1g -Xmx12g -XX:+UseLargePages -XstartOnFirstTh ...

  2. ctr命令的基本使用与技巧

    k8s早1.24后放弃docker,并把containerd作为运行时组件,containerd 调用链更短,组件更少,更稳定,占用节点资源更少 ctr是containerd的一个客户端工具 cric ...

  3. shell 读取文件内容到数组

    在 shell 脚本中,可以使用下面的语法来读取文件内容并将其存储到数组中:   bash 复制代码 array=() while read line; do array+=("$line& ...

  4. Java保留两位小数的几种写法总结

    转载 本文列举了几个方法: 1. 使用java.math.BigDecimal 2. 使用java.text.DecimalFormat 3. 使用java.text.NumberFormat 4. ...

  5. Qt编写ERP库存库房发货电子看板

    一.前言 有了之前可视化大屏电子看板系统的经验,要做这个ERP库存库房发货电子看板,可以说是水到渠成轻车熟路,技术难度比之前做过的各种大屏系统的子模块都要简单.由于库存库房发货数据的特殊性,基本上都是 ...

  6. Qt音视频开发19-海康sdk录像存储

    一.前言 关于调用海康sdk来进行录像存储,整体的框架架构处理流程沿袭了之前vlc内核.ffmpeg内核.mpv内核的做法,定时存储这块,开个定时器判断,到了时间则先关闭原来的录像存储,然后在开始一个 ...

  7. 2025年了,你还不会配置Jetson Orin NX嘛?

    2025年了,你还不会配置Jetson Orin NX嘛? 我的设备为:Jetson Orin NX 16G + JetPack6.1+达妙科技载板 帅气的Jetson Orin NX拿到手了,都20 ...

  8. JavaScript中find()和 filter()方法的区别小结

    前言 JavaScript 在 ES6 上有很多数组方法,每种方法都有独特的用途和好处. 在开发应用程序时,大多使用数组方法来获取特定的值列表并获取单个或多个匹配项. 在列出这两种方法的区别之前,我们 ...

  9. MySql 建表出现的问题 : [ERR] 1064 - You have an error in your SQL syntax; check the manual.......

    使用 MySql 建表出现的问题 在使用 Navicat Premium 运行 sql 语句进行建表时,MySQL 报错如下: 1064 - You have an error in your SQL ...

  10. 编译WorldWindJavav2.1.0中的ApplicationTemplate.java文件时有时报出“Exception in thread "main" java.lang.AbstractMethodError: javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)V”错误的解决办法

    1 问题 编译WorldWindJavav2.1.0中的ApplicationTemplate.java文件时有时报出如下错误: Exception in thread "main" ...