花3分钟来了解一下Vue3中的插槽到底是什么玩意
前言
插槽
看着是一个比较神秘的东西,特别是作用域插槽
还能让我们在父组件里面直接访问子组件里面的数据,这让插槽变得更加神秘了。其实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
指令给header
和footer
插槽传递内容。
来看看编译后的父组件
我们在浏览器中来看看编译后的父组件代码,简化后如下:
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
函数时传入了第三个参数是一个对象。对象中包含header
、footer
、default
三个方法,这三个方法对应的是子组件
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对象好像有点熟悉,这个对象中包含default
、footer
、header
这三个方法,其实这个slots对象就是前面我们讲的父组件中定义的那个对象,执行对象的default
、footer
、header
方法就会生成对应插槽的虚拟DOM。
前面我们讲了在父组件中会定义default
、footer
、header
这三个方法,那这三个方法又是在哪里执行的呢?
答案是:在子组件里面执行的。
在执行_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
对象。在子组件中会去执行这些方法,并且可以将子组件的变量传给父组件,由父组件去接收参数,这就是作用域插槽
的原理。了解了这个后当我们在useSlots
、jsx
、tsx
中定义和使用插槽就不会那么迷茫了。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
花3分钟来了解一下Vue3中的插槽到底是什么玩意的更多相关文章
- 花3分钟了解下C/C++中的函数可变参简单实现
1.可变参函数的原理 C/C++函数的参数是存放在栈区的,并且参数的入栈是从参数的右边开始,即最后一个参数先入栈,而第一个参数最后才入栈,所以,根据栈的后进先出性质,函数总能找到第一个参数.所以,可变 ...
- 花十分钟,让你变成AI产品经理
花十分钟,让你变成AI产品经理 https://www.jianshu.com/p/eba6a1ca98a4 先说一下你阅读本文可以得到什么.你能得到AI的理论知识框架:你能学习到如何成为一个AI产品 ...
- 【MySQL】花10分钟阅读下MySQL数据库优化总结
1.花10分钟阅读下MySQL数据库优化总结http://www.kuqin.com2.扩展阅读:数据库三范式http://www.cnblogs.com3.my.ini--->C:\Progr ...
- 【三分钟视频教程】iOS开发中 Xcode 报 apple-o linker 错误的#解决方案#
[三分钟视频教程]iOS开发中 Xcode 报 apple-o linker 错误的#解决方案# 同样的道理,指向同一库文件的代码语句如果重复书写,即使重复书写所在的文件名字不同,同样会造成这 ...
- 【黑科技】花几分钟和孩子动手DIY,即可用手机完成全息影像!
http://baobao.sohu.com/20160902/n467277059.shtml [黑科技]花几分钟和孩子动手DIY,即可用手机完成全息影像! 杭州亲子圈2016-09-02 07:2 ...
- vue3中使用axios如何去请求数据
在vue2中一般放在created中,但是在vue3中取消了created生命周期,请求方式有两种 直接在setup中去获取数据 setup(props) { const data = reactiv ...
- vue3中watch函数
watch 监听普通类型 let count = ref(1); const changeCount = () => { count.value+=1 }; watch(count, (newV ...
- 端午总结Vue3中computed和watch的使用
1使用计算属性 computed 实现按钮是否禁用 我们在有些业务场景的时候,需要将按钮禁用. 这个时候,我们需要使用(disabled)属性来实现. disabled的值是true表示禁用.fals ...
- vue 3 学习笔记 (七)——vue3 中 computed 新用法
vue3 中 的 computed 的使用,由于 vue3 兼容 vue2 的选项式API,所以可以直接使用 vue2的写法,这篇文章主要介绍 vue3 中 computed 的新用法,对比 vue2 ...
- 在vue3中使用router-link-active遇到的坑
在使用 router-link-active 设置链接激活时CSS类名时,发现在例如 /member/order 和 /member/order/:id 这两个都包含 /member/order的路由 ...
随机推荐
- git恢复到之前提交的记录
项目搞崩了,还提交上去了怎么办? 那当然是恢复到之前的提交记录了,那怎么操作呢? 首先,到代码托管平台找到你想恢复的提交记录(在此以github为例) 获取 commit id 首先,通过如下图操作获 ...
- 【C#】【平时作业】习题-7-继承、抽象与多态
相关概念 什么是继承 继承定义了如何根据现有类创先新类的过程 任何类都可以从另外一个类继承 一个派生出来的子类具有这个类的所有公共属性和方法 类的继承机制 创建新类所根据的基础类称为基类或父类,新建的 ...
- 【shell】远程执行shell|多节点并行执行shell|远程执行注意
目录 前提条件 shell远程执行 多节点上并行执行命令的三种方法 方法1 使用bash执行命令 方法2 使用clustershell执行命令--还能收集结果 方法3 使用pdsh 执行命令 远程执行 ...
- Mac netstat 查看端口报错 netstat: option requires an argument -- p 解决
netstat -anvp |grep 10001 查询端口的时候报错提示 意思是缺少协议. 解决方案在Mac上正确使用的方法是:即-f需要加上地址族,-p需要加上协议TCP或者UDP等 a)如果需要 ...
- 【OpenCV】features2d_converters.cpp:2:10: fatal error: common.h: 没有那个文件或目录
Linux环境下使用opencv的dnn模块调用yolov4遇到的坑(纯CPU)一.问题描述Ubuntu安装opencv4.4,第一次编译完成安装成功,发现编译时少加了几个选项,于是重新编译,结果报如 ...
- [转]快速搭建简单的LBS程序——地图服务
很多时候,我们的程序需要提供需要搭建基于位置的服务(LBS),本文这里简单的介绍一下其涉及的一些基本知识. 墨卡托投影 地图本身是一个三维图像,但在电脑上展示时,往往需要将其转换为二维的平面图形,需要 ...
- KES的执行计划分析与索引优化
今天我们继续探讨国产数据库KES的相关内容,本次的讨论重点将放在SQL优化的细节上.作为Java开发人员,我们通常并不需要深入了解数据库的底层实现细节,而是更多地关注如何提升应用性能与数据库的交互效率 ...
- 史上最详细idea提交代码到github教程
史上最详细idea提交代码到github教程步骤前言github上创建空项目 idea上代码关联本地gitidea上代码本地提交解决Push rejected: Push to origin/mast ...
- superset 1.3版本WIN10安装实录
首先说下,为什么要这么做,因为二开需要,二开要有源码,然后对源码修改,编译,所以不能通过类似https://zhuanlan.zhihu.com/p/271695878这种方式,直接安装: 1.去Gi ...
- VS中无法识别unistd.h的问题
问题 VS 无法打开源文件 unistd.h 参考:链接 方法 许多在Linux下开发的C程序都需要头文件unistd.h,但VC中没有个头文件,所以用VC编译总是报错.把下面的内容保存为unistd ...