前言

插槽看着是一个比较神秘的东西,特别是作用域插槽还能让我们在父组件里面直接访问子组件里面的数据,这让插槽变得更加神秘了。其实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. 【服务器安全问题】双防火墙有什么意义?SSH暴力破解如何防范

    发现我的服务器有大量的陌生IP尝试SSH登录,似乎想暴力破解. 我个人习惯是服务器提供商开放了所有端口的,同时也没有开启运维软件提供的防火墙emm 这倒是让我想到了 一般服务器提供商(比如阿里云.华为 ...

  2. Java多线程处理文件详解与代码示例

    在Java编程中,文件处理是一项常见的任务.当需要处理大量文件或处理文件的时间较长时,单线程的处理方式可能会显得效率低下.为了提高文件处理的效率,我们可以使用多线程技术.本文将详细介绍如何使用Java ...

  3. Qt4/5升级到Qt6吐血经验总结V202308

    00:直观总结 增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理. 把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能. 把一些Qt5中兼容Qt4的 ...

  4. Qt音视频开发21-mpv内核万能属性机制

    一.前言 搞过vlc内核后又顺带搞了搞mpv内核,mpv相比vlc,在文件数量.sdk开发便捷性方面绝对占优势的,单文件(可能是静态编译),不像vlc带了一堆插件,通过各种属性来set和get值,后面 ...

  5. Qt编写物联网管理平台39-报警联动

    一.前言 本系统支持报警联动,就是某个探测器报警后,再去下发命令,通知下面的继电器警号,一般是通过串口发送,由于现场会利用现有的串口线路比如485总线,所以本系统需要做特殊处理,就是公用485通信总线 ...

  6. findHomography()函数详解

    indHomography: 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法 函数功能:找到两个平面之间的转换矩阵. Mat cv::findHom ...

  7. 是时候弃用ibSass和node-sass而采用Dart Sass了!

    背景说明 此次改动是在 Sass 核心团队进行了大量讨论之后,得出的结论,现在是时候正式宣布弃用 LibSass 和基于它构建的包(包括 Node Sass).多年来,LibSass 显然没有足够的工 ...

  8. 即时通讯技术文集(第14期):WebSocket精华文章合集 [共15篇]

    为了更好地分类阅读52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第14 期. [- 1 -] 新手快速入门:WebSocket简明教程 [链接] http://w ...

  9. DateTimeExtensions:一个轻量C#的开源DateTime扩展方法库

    推荐一个专门为System.DateTime编写的扩展方法库. 01 项目简介 该项目主要是为System.DateTime和System.DateTimeOffset的编写的扩展方法,包括自然日期差 ...

  10. JIRA/Jira-cloud Rest API

    官方参考: https://developer.atlassian.com/cloud/jira/platform/rest/v3/ 记录部分有用的 获取用户: /rest/api/2/users/s ...