花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的路由 ...
随机推荐
- 【Python】【MySQL】Python将JSON数据以文本形式存放到MySQL的Text类型字段中
1.起因 在做一个自动打卡的玩意.登录会得到那个平台一系列的信息.我又不想专门修改.增加数据库字段来存放,所有打算直接将返回的JSON数据保存到一个MySQL字段中. 内容肯定不能直接放,考虑下比如数 ...
- Qt通用方法及类库1
函数名 //桌面宽度高度 static int deskWidth(); static int deskHeight(); //程序文件名称+当前所在路径 static QString appName ...
- C#中如何将图片添加为程序的资源
C#中将图片添加为程序的资源的步骤: 1.在C#程序的"Properties"文件夹中双击Resources.resx文件,以便打开资源文件,使其处于可编辑状态: 2.在打开后的R ...
- Python中的包、模块和源码的组织关系
- Visual Studio2012编译C#项目时出错“LC.exe”已退出的解决方法
症状: Visual Studio2012编译C#项目时出错"LC.exe"已退出,代码为 -1. 原因: 因为证书的原因,把项目中"properties"目录 ...
- 网络编程懒人入门(十四):到底什么是Socket?一文即懂!
本文由cxuan分享,原题"原来这才是 Socket",有修订. 1.引言 本系列文章前面那些主要讲解的是计算机网络的理论基础,但对于即时通讯IM这方面的应用层开发者来说,跟计算机 ...
- kubernetes系列(二) - kubectl的入门操作
目录 1. 安装 / 卸载 1 .1 前提条件 1.2 安装方式 1.3 卸载 2. 通过 minikube 学习 k8s 实操基础 2.1 创建集群 2.2 部署应用 2.3 探索当前应用[故障排除 ...
- 一种调试 线段树 / Treap / Splay / 左偏树 / LCT 等树形结构的技巧
前言 如果我们需要观察程序运行过程中,某一个变量.某一个序列的变化情况,你可以在修改的地方打断点 debug,或者直接在需要的地方输出就行了. 但是对于一些树形结构,我们不好将其直观地呈现出来,常常只 ...
- Final Review - 返回天空的雨滴
目录 Motivations Tricks Conclusions Algorithms And - \[\text{Each moment, now night.} \newcommand{\vct ...
- uwp 图像处理例子
async void test() { Color replaceBlack = Color.FromArgb(224,233,55,6); Color replaceWhite = Color.Fr ...