前言
不小心接触了vue-template-compiler
,所以借助他看一下底层编译出来的数据来探寻vue源码,接触的依然是最外层的源码(o_o)
slot插槽的概念
官方说明
个人理解
插槽就是在子组件里做定制化内容,当一个复用的组件内有一部分需要针对不同的场景有所差异,且无规律可循时,我们就可以使用<slot></slot>
让父组件自定义内容。
这种时候又可以分为两个场景:
- 内容与子组件内的数据无关
- 内容与子组件内的数据有关
前者使用普通插槽,后者使用作用域插槽,作用域插槽的作用就是拿到子组件的数据
具体介绍及使用可参考官方文档
https://cn.vuejs.org/v2/guide/components-slots.html
区别:
在父组件初始化期间会编译成文本子节点存起来,在子组件渲染的时候直接将插槽替换成父组件里渲染的节点
在父组件初始化期间会编译成一个函数,在子组件初始化的时候执行这个函数生成vnode再进行渲染
了解一下vue的渲染函数的缩写
像
_c(createElement)
,_t(renderSlot)
,_v(createTextVNode)
是下面会常看到的
普通插槽
r1
为子组件,r2
为父组件
let r1 = VueTemplateCompiler.compile('
<div>
<span>hello</span>
<slot name="header">name</slot>
</div>
');
let r2 = VueTemplateCompiler.compile('
<div>
<span>hello</span>
<div slot="header">name</div>
</div>
');
先看父组件渲染结果
r2.render=
'with(this){
return _c(\'div\',[
_c(\'span\',[
_v("hello")
]),
_c(\'div\',{ //在编译时将插槽已经编译成虚拟节点存进[_v("name")]
attrs:{
"slot":"header"
},
slot:"header"
},[_v("name")])
])
}'
下面是子组件的render函数
r1.render=
'with(this){
return _c(\'div\',[
_c(\'span\',[
_v("hello")
]),
_t("header",[_v("name")]) //在渲染时将header替换成父组件编译完成的[_v("name")]
],2)
}'
可以看出父组件在编译组件时就将slot插槽内容调用_c方法编译成节点了,并用_v("name")
将文件节点保存,当子组件初始化时,会调_t()
方法渲染插槽,会先去父组件中寻找名为"header"
的插槽,将"header"
替换成_v("name")
,渲染过程就是将vnode渲染成真实Dom
作用域插槽
r3
为子组件,r4
为父组件
let r3 = VueTemplateCompiler.compile('
<div>
<span>hello</span>
<slot name="footer" a="1"></slot>
</div>
');
let r4 = VueTemplateCompiler.compile('
<div>
<span>hello</span>
<div slot="footer" slot-scope="msg">{{msg.a}}</div>
</div>
');
先看父组件
r4.render=
'with(this){
return _c(\'div\',{
scopedSlots:
_u([{
key:"footer",
fn:function(msg){ //将插槽编译成函数
return _c(\'div\',{},[_v(_s(msg.a))])
}
}])
},
[_c(\'span\',[_v("hello")])]
)
}'
子组件
r3.render=
'with(this){
return _c(\'div\',[
_c(\'span\',[_v("hello")]),
_t("footer",null,{a:"1"})
],2)
}'
父组件编译时将插槽编译成函数存进数组里,子组件初始化时会根据插槽名"footer"
去父组件里找key
为"footer"
的函数并执行,然后生成vnode进行渲染
在vue2.6.0以后,旧版的slot、slot-scope的使用就被废弃了,但是还可以用,推荐统一使用v-slot
我用作用域插槽用的最多的还是在使用组件库的时候,可见大型组件库内部留了很多插槽,便于客户去定制化使用
我们在封装组件的时候也可以去考虑使用哪种插槽能符合更多场景,提高复用性