vue3 v-slot,v-slot插槽
本文主要介绍Vue3 Slot槽的实现原理的详细说明。有需要的朋友可以借鉴一下,希望能有所帮助。祝大家进步很大,早日升职加薪。
目录
Vue官方对槽的定义到底什么是槽?如何用slot复习组件渲染原理?slot的初始化原理来分析slot中的内容?范围槽原则命名槽原则默认内容槽原则
Vue官方对插槽的定义
Vue实现了一套用于内容分发的API。这个API的设计灵感来源于Web组件的规范草案,slot元素作为承载分布式内容的出口。
Slot到底是什么
那么槽到底是什么?Slot实际上是一个接受父组件传递的slot内容,然后生成VNode并返回的函数。
通常,我们使用slot/slot标签来接受父组件传输的内容。然后标签最终编译好之后,就是一个创建VNode的函数,我们可以称之为创建slot VNode的函数。
//slot/slot标签由vue3编译。
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
return _renderSlot(_ctx。$slots,默认)
}
我们可以清楚的看到slot/slot标签被Vue3编译后变成了一个名为_renderSlot的函数。
如何使用插槽
要使用插槽,必须有父子组件。
假设父组件如下:
待办事项按钮
添加待办事项
/todo-button
我们在父组件中使用了todo-button的子组件,并传递了Add todo的槽内容。
todo-按钮子组件模板内容
button class=btn-primary
插槽/插槽
/按钮
当组件被渲染时,slot/slot将被替换为“Add todo”。
回顾组件渲染的原理
那么背后的原理是什么呢?在了解slot的底层原理之前,我们还需要回顾一下Vue3的组件运行原理。
组件的核心是可以产生一堆VNode。对于Vue来说,一个组件的核心是它的渲染函数,组件的挂载本质是执行渲染函数,得到要渲染的VNode。至于data/props/computed,它为渲染函数产生VNode提供数据源服务,关键是组件最终产生的VNode,因为这是需要渲染的内容。
插槽的初始化原理
Vue3在渲染VNode时发现VNode的类型是组件类型,就会经历组件渲染的过程。组件渲染的过程是先创建一个组件实例,然后初始化组件实例。当组件实例初始化时,将处理与Slot相关的内容。
源代码的runtime-core\src\component.ts中
在initSlots函数中初始化组件槽的相关内容。
那么initSlots函数看起来像什么,它做了什么?
runtime-core \ src \ component slots . ts
首先要确定这个组件是不是槽组件,那么如何确定这个组件是不是槽组件呢?让我们回过头来看看上面父组件的编译代码:
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
const _ component _ todo _ button=_ resolve component( todo-button )
return (_openBlock(),_ create block(_ component _ todo _ button,null,{
默认值:_ witchtx(()=[
_createTextVNode( Add todo )
],未定义,真),
_: 1 /*稳定*/
}))
}
我们可以看到Slot组件的子内容是一个对象类型,它是下面的代码:
{
默认值:_ witchtx(()=[
_createTextVNode( Add todo )
],未定义,真),
_: 1 /*稳定*/
}
然后,在创建这个组件的VNode时,我们会判断它的子节点是否是Object类型。如果是Object类型,我们会在这个组件的VNode的shapeFlag上挂一个Slot组件标志。
如果它是通过模板编译的,那么它就是标准的slots子元素,带有_ attribute,可以直接放在组件实例上。
如果slot对象是用户自己写的,那么就没有_ attribute,所以需要规范化和规格化。
如果用户的骚操作不遵循规范,那就走normalizeVNodeSlots流程。
解析插槽中的内容
我们先看看子组件编译之后的代码:
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
return (_openBlock(),_createElementBlock(button ,{ class: btn-primary },[
_renderSlot(_ctx .$老虎机,默认)
]))
}
上面我们也讲过了插槽/插槽标签被vue3编译之后的就变成了一个叫_renderSlot的函数。
渲染槽函数接受五个参数,第一个是实例上的插槽函数对象插槽,第二个是插槽的名字,也就是将插槽内容渲染到指定位置,第三个是插槽作用域接收的道具,第四个是插槽的默认内容渲染函数,第五个暂不太清楚什么意思。
作用域插槽原理
作用域插槽是一种子组件传父组件的传参的方式,让插槽内容能够访问子组件中才有的数据。
子组件模板
狭槽用户名= coboy /插槽
编译后的代码
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
return _renderSlot(_ctx .$slots, default ,{ username: coboy })
}
父组件模板
待办事项按钮
模板v-slot:default=slotProps
{{ slotProps.username }}
/模板
/todo-button
编译后的代码
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
const _ component _ todo _ button=_ resolve组件( todo-button )
return (_openBlock(),_ create block(_ component _ todo _ button,null,{
默认值:_ witchtx((老虎机道具)=[
_ createTextVNode(_ toDisplayString(slot props。用户名),1 /* TEXT */)
]),
_: 1 /*稳定*/
}))
}
上面讲过渲染槽函数,可以简单概括成下面的代码
导出函数渲染插槽(插槽,名称,道具){
const slot=slots[name]
如果(插槽){
如果(槽类型===函数){
return createVNode(Fragment,{},slot(props))
}
}
}
时间是组件实例上传过来的插槽内容,其实就是这段内容
{
默认值:_ witchtx((老虎机道具)=[
_ createTextVNode(_ toDisplayString(slot props。用户名),1 /* TEXT */)
]),
_: 1 /*稳定*/
}
名字是默认,那么插槽[名称]得到的就是下面这个函数
_ witchtx((老虎机道具)=[
_ createTextVNode(_ toDisplayString(slot props。用户名),1 /* TEXT */)
])
插槽(道具)就很明显是插槽({用户名: coboy }),这样就把子组件内的数据传到父组件的插槽内容中了。
具名插槽原理
有时我们需要多个插槽。例如对于一个带有如下模板的基本布局组件:
div class=容器
页眉
!-我们希望把页头放这里-
/页眉
主要的
!-我们希望把主要内容放这里-
/main
页脚
!-我们希望把页脚放这里-
/页脚
/div
对于这样的情况,插槽元素有一个特殊的属性:名称。通过它可以为不同的插槽分配独立的ID,也就能够以此来决定内容应该渲染到什么地方:
!-子组件-
div class=容器
页眉
插槽名称=标题/槽
/页眉
主要的
插槽/插槽
/main
页脚
插槽名称=页脚/插槽
/页脚
/div
一个不带名字的狭槽出口会带有隐含的名字默认。
在向具名插槽提供内容的时候,我们可以在一个模板元素上使用v形槽指令,并以v形槽的参数的形式提供其名称:
!-父组件-
基本布局
模板v型槽:标题
氕标题/h1
/模板
模板v形槽:默认
p默认/p
/模板
模板v形槽:页脚
pfooter/p
/模板
/base-布局
父组件编译之后的内容:
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
const _ component _ base _ layout=_ resolve组件( base-layout )
return (_openBlock(),_ create block(_ component _ base _ layout,null,{
header:_ witchtx(()=[
_createElementVNode(h1 ,null, header )
]),
默认值:_ witchtx(()=[
_createElementVNode(p ,null, default )
]),
页脚:_ witchtx(()=[
_createElementVNode(p ,null, footer )
]),
_: 1 /*稳定*/
}))
}
子组件编译之后的内容:
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
return (_openBlock(),_createElementBlock(div ,{ class: container },[
_createElementVNode(header ,null,[
_renderSlot(_ctx。$slots, header )
]),
_createElementVNode(main ,null,[
_renderSlot(_ctx。$slots,默认)
]),
_createElementVNode(footer ,null,[
_renderSlot(_ctx。$插槽,页脚)
])
]))
}
我们可以通过子组件的编译内容看到这三个槽渲染函数。
_renderSlot(_ctx。$slots, header )
_renderSlot(_ctx。$slots,默认)
_renderSlot(_ctx。$插槽,页脚)
然后我们来回顾一下renderSlot渲染函数。
//简化Renderslots
导出函数renderSlots(插槽,名称,道具){
const slot=slots[name]
如果(插槽){
if(槽类型===函数){
return createVNode(Fragment,{},slot(props))
}
}
}
这时候我们就可以清楚的知道,所谓的命名函数是通过renderSlots渲染函数的第二个参数来定位要渲染的父组件提供的槽内容。父组件的槽内容在编译后成为对象数据类型。
{
header:_ witchtx(()=[
_createElementVNode(h1 ,null, header )
]),
默认值:_ witchtx(()=[
_createElementVNode(p ,null, default )
]),
页脚:_ witchtx(()=[
_createElementVNode(p ,null, footer )
]),
_: 1 /*稳定*/
}
默认内容插槽的原理
在大多数情况下,我们可能希望在这个按钮中呈现“提交”文本。要使用“Submit”作为替代内容,我们可以将它放在slot标签中。
按钮类型=提交
插槽提交/插槽
/按钮
现在,当我们在父组件中使用submit-button而不提供任何槽内容时:
lt;提交按钮gt。lt;/submit-buttongt。
将呈现替代内容“提交”:
按钮类型=提交
使服从
/按钮
但是如果我们提供内容:
提交按钮
救援
/提交按钮
则该提供的内容将被呈现以替换替代内容:
按钮类型=提交
救援
/按钮
这是什么原理?我们来看看上面默认内容槽的编译代码。
导出函数render(_ctx,_cache,$props,$setup,$data,$options) {
return (_openBlock(),_createElementBlock(button ,{ type: submit },[
_renderSlot(_ctx。$slots, default ,{},()=[
_createTextVNode(Submit )
])
]))
}
我们可以看到槽函数的内容如下
_renderSlot(_ctx。$slots, default ,{},()=[
_createTextVNode(Submit )
])
让我们回头看看renderSlot函数。
renderSlot函数接受五个参数,第四个是插槽的默认内容呈现函数。
我们可以从renderSlot函数的源代码中看到,
第一步是获得由父组件提供的内容槽的内容,
步骤二,如果父组件提供了槽位内容,则使用父组件提供内容槽位,否则,执行默认内容呈现功能以获得默认内容。
以上是Vue3槽实现原理的详细内容。更多关于Vue3槽的信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。