You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// compile 阶段的生成AST树过程// 省略部分代码functionprocessSlotContent(el){// slot="xxx"constslotTarget=getBindingAttr(el,"slot");if(slotTarget){el.slotTarget=slotTarget==='""' ? '"default"' : slotTarget;el.slotTargetDynamic=!!(el.attrsMap[":slot"]||el.attrsMap["v-bind:slot"]);}// 2.6 v-slot syntaxif(el.tag==="template"){// v-slot on <template>constslotBinding=getAndRemoveAttrByRegex(el,slotRE);if(slotBinding){const{ name, dynamic }=getSlotName(slotBinding);el.slotTarget=name;el.slotTargetDynamic=dynamic;el.slotScope=slotBinding.value||emptySlotScopeToken;// 新的slot有slotScope}}}// compile 阶段的 ast 过程// 省略部分代码functioncloseElement(element){if(currentParent&&!element.forbidden){if(element.elseif||element.else){processIfConditions(element,currentParent);}else{if(element.slotScope){// scoped slot// keep it in the children list so that v-else(-if) conditions can// find it as the prev node.varname=element.slotTarget||'"default"';(currentParent.scopedSlots||(currentParent.scopedSlots={}))[name]=element;}currentParent.children.push(element);element.parent=currentParent;}}}// compile 阶段的 codegen 过程// 省略部分代码functiongenData(el,state){if(el.slotTarget&&!el.slotScope){data+=`slot:${el.slotTarget},`;}// scoped slotsif(el.scopedSlots){data+=`${genScopedSlots(el,el.scopedSlots,state)},`;}}
最近工作比较忙,有想写的博客,但是一直没有下笔,想来也是有点懒了,还是要拔拔草。正值金三银四的,面试别人也以
Vue
源码居多,想要还是有必要学习一下插槽相关的内容。这次的
bug
私以为还是Vue
本身的问题,先看看一下代码:可以看到上面的
App.js
采用jsx
的写法,由于ChildContainer.vue
只有一个默认的slot
,而App.js
则同时通过scopedSlots
和children
的方式传入了插槽内容。首先子组件里面没有使用到a
插槽,所对应的内容不会渲染出去,最后会渲染出默认的内容为default外的默认内容
。此时表现一切都是正常的,
this.slots
和slotsData.default
各施其职,而且还充分利用了computed
的缓存功能,避免重复的计算slots
。而当加载后,修改slotsData.default
数据的发现,如this.slotsData.default.push('slotsData的deflaut内容')
,可以看到页面没有任何变化,难道是设置的姿势不对?这是再简单不过的,查看slotsData.default
数据也是对的,只是为什么不渲染出来呢?于是换成$set
来设置,已经是最完整的了,只是还是没有用。渲染函数确实再次执行了,但是输出的内容还是default外的默认内容
,问题出在哪里呢?这个时候把
default外的默认内容
这一行代码注释掉,发现再次设置slotsData.default
的时候,数据生效了,同时页面有渲染slotsData的deflaut内容
,岂不是奇了怪了。之前通过Vue Dev Tool
还可以看到生成的this.slots
这个computed
内容多了个_normalized
字段,而且其下面还有个default
函数,当注释default外的默认内容
这一行的时候,这个_normalized
也没有了,什么时候多了这个字段呢?看来只能看看Vue
的源码,之前看的时候一直避开slot
部分的,完全是个黑盒。组件输出的渲染函数的
slot
部分为_vm._t("default")
, 其中_t
就是如下函数:可以看到
renderSlot
的最终输出取决于vm.$scopedSlots
,没有的话再是vm.$slots
,而$scopedSlots
的生成取决于_parentVnode.data.scopedSlots
节点vnode
数据的scopedSlots
字段,也就是上文业务中的this.slots
;vm.$slots
实例自身的生成的$slots
,一般是通过resolveSlots
解析标签来匹配获得实例的slots
节点;vm.$scopedSlots
前一个$scopedSlots
;在上面例子中,当更新
default
的数据的时候,_parentVnode.data.scopedSlots
和default
数据没有关系所以不会更新,而vm.$slots
则是包含了更新了的default
插槽的数据,也就是包含了default外的默认内容
以及slotsData的deflaut内容
两个节点,只是在 debug 过程中发现最后生成的vm.$scopedSlots
有大大的问题。先看看
normalizeScopedSlots
方法首次加载的时候,由于父节点的
scopedSlots
是一个computed
返回的对象,最后会将生成的res
赋值给scopedSlots.__normalized
,而这个res
也包含了vm.$slots
部分,也就是原本通过computed
传入的对象是不包含default
插槽的,但是res
是全部的内容,也就会包含default
内容,渲染内容为default外的默认内容
的节点,最后会被挂载到computed
输出值的_normalized
字段。首次渲染自然是没有问题的,因为
_normalized
也是最新的。当第二次执行normalizeScopedSlots
的时候,由于computed
缓存,这个_normalized
字段也被缓存下来了,由于存在_normalized
,会返回上一次生成的default
数据,不会包含最新数据的vm.$slots
的数据返回,在后续的renderSlot
一直是获取老的数据。通过测试将
slots
从computed
变成methods
,问题就解决了,那这应该是算Vue
的bug
了,没有设想到传入的scopedSlots
是一个缓存值。只是要使用的话如何好呢,有两个方法:jsx
组件里面嵌套插槽的写法,全部写在jsx
的scopedSlots
里面;这样每次更新插槽数据,都会重新触发computed
从而更新jsx
的scopedSlots
,只是这样一个插槽更新了,所有的插槽都要计算一次,效果还是稍微差了一点;renderSlot
的渲染,那如果采用jsx
指定插槽呢。比如this.$slots.default
这样岂不是快哉,只是上面的还缺了点,还需要$scopedSlots
,所以应该是this.$scopedSlots.default ? this.$scopedSlots.default(props) : this.$slots.default
scopedSlots 与 slots 如何区分?
scopedSlots
与slots
是两个不同的部分,正如字面意思,前者是作用域插槽,后者是插槽,但是呢,具体区分更多的是按照 2.6 版本来的,比如如下写法:表面是是没有看到作用域的,但是采用了 2.6 的新写法,最后通过编译会输出
scopedSlots
到vnode
:可以看到
processSlotContent
里面上半部分是对slot=name
判断,是对老的写法的处理,而下面部分则是对 2.6 版本的新写法template
与v-slot
组合的处理,新的部分最后输出包含了slotScope
字段,而旧的版本没有。由于上面例子没有设置作用域,所以slotScope
为emptySlotScopeToken
也就是_empty_
字符串。在随后的closeElement
里面,若有slotScope
,则会将其设置到父节点的scopedSlots
里面,形成一个插槽对象。到这里都是生成
AST
的过程,后面codegen
阶段,会根据新老写法的不同,生成slot
与scopedSlots
数据,其中genScopedSlots
返回的是编译好的渲染函数,而slot
则不同,插槽内容,以children
的形式存在与父节点中,只是其属性有slot
而已。生成的
scopedSlots
数据会传入到vnode
里面,最后传入上面提到的normalizeScopedSlots
返回给实例的$scopedSlots
,而$slot
则会根据前面传入的vnode
的slot
数据生成。上面是父组件里面生成
ChildContainer
的插槽信息,包括生成scopedSlots
数据这些,而最后在ChildContainer
编译阶段,会根据slot
标签名的不同生成对应的VNode
,方法如下:可以看到有
$scopedSlots
就会直接输出,没有会出采用$slots
的数据,fallback
则是默认的插槽内容。最后返回的是VNode
数据,给到子组件。最后在
normalizeScopedSlots
可以发现,$slots
的也是会传入$scopedSlots
里面的,所以项目中直接用$scopedSlots
就可以了,同时_parentVnode.data.scopedSlots
数据也会传给$slots
里面的,某种程度说,是$scopedSlots
和$slots
区别不大的。总结
跟着问题学习源码,还是很快的,只是觉得,自己还在看
Vue
源码的,有点不太行,一直想要突破到别的领域的,看来遥遥无期。The text was updated successfully, but these errors were encountered: