Vue.component('button-counter', {
template: '<div> <slot>我是默认内容</slot></div>'
})
复制代码
new Vue({
el: '#app',
template: '<button-counter><span>我是slot传入内容</span></button-counter>'
})
复制代码
这里先注册一个button-counter
组件,而后使用它,上面渲染出来的结果是 我是slot内容node
原理解析:git
1.这里直接看到组件button-counter
编译后的render
函数,就不详细从template
模板编译提及,直接看最后的render函数:github
(function anonymous(
) {
with(this){return _c('div',[_t("default",[_v("我是默认内容")])],2)}
})
复制代码
这里_v
就是建立普通文本节点,主要看_t
函数,这里_t
也就是renderSlot
函数的简写数组
function installRenderHelpers (target) {
...
target._t = renderSlot;
...
}
复制代码
function renderSlot (
name,
fallback,
props,
bindObject
) {
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
nodes = scopedSlotFn(props) || fallback;
return nodes;
}
复制代码
这里把函数renderSlot
函数简化下,其它状况先不看,这里会把name
和fallback
传进来, 这里先说一下name
属性,咱们上面定义是bash
Vue.component('button-counter', {
template: '<div><slot>我是默认内容</slot></div>'
})
复制代码
这里slot
没有给props
为name
的值,因此默认是default
,咱们能够给它一个name
值,例如app
Vue.component('button-counter', {
template: '<div><slot name="header">我是默认内容</slot></div>'
})
复制代码
那么使用时函数
new Vue({
el: '#app',
template: '<button-counter><span slot="header">我是slot传入内容</span></button-counter>'
})
复制代码
可是上面的用法slot
在2.6.0版本已经废弃,提供了新的v-slot
代替,可是你仍然能够这么写,源码中依然保存对slot的兼容处理,咱们看下用v-slot
的写法以及须要注意的地方ui
new Vue({
el: '#app',
template: '<button-counter><template v-slot:header><span>我是slot传入内容</span></template></button-counter>'
})
复制代码
注意这里v-slot
须要使用在template
,不可再跟上面同样直接做用于span
标签上面this
回到上面说的renderSlot
函数,name
这里是默认值default
,第二个参数fallback
就是咱们在组件中的slot
节点的默认值spa
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
nodes = scopedSlotFn(props) || fallback;
return nodes;
复制代码
若是this.$scopredSlots
存在该name
的值,则调用该返回函数生成一个nodes
节点返回,不然返回fallback
(即默认值),因此到这里咱们知道为何在自定义组件中有传入子元素就渲染子元素,没有就使用默认插槽里面的值了,这里涉及到this.$scopredSlots
这个变量,咱们接下来看下这个值,咱们首先看下vm.$slots
当组件在执行initRender
函数时
function initRender (vm) {
...
vm.$slots = resolveSlots(options._renderChildren, renderContext);
...
}
复制代码
resolveSlots
函数会对children
节点作归类和过滤处理,返回slots
function resolveSlots (
children,
context
) {
if (!children || !children.length) {
return {}
}
var slots = {};
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
var data = child.data;
// remove slot attribute if the node is resolved as a Vue slot node
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot;
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
// 若是slot存在(slot="header") 则拿对应的值做为key
var name = data.slot;
var slot = (slots[name] || (slots[name] = []));
// 若是是tempalte元素 则把template的children添加进数组中,这也就是为何你写的template标签并不会渲染成另外一个标签到页面
if (child.tag === 'template') {
slot.push.apply(slot, child.children || []);
} else {
slot.push(child);
}
} else {
// 若是没有就默认是default
(slots.default || (slots.default = [])).push(child);
}
}
// ignore slots that contains only whitespace
for (var name$1 in slots) {
if (slots[name$1].every(isWhitespace)) {
delete slots[name$1];
}
}
return slots
}
复制代码
这里的_renderChildren
就是把children
的name
相同的VNodes
节点归类放到一个数组中,最终返回了一个对象,key
为对应的name
,值是包含该name
对应的节点数组
接下来的_render
函数的时候,经过normalizeScopedSlots
获得vm.$scopedSlots
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
复制代码
有时咱们须要获取子组件的一些内部数据,好比获取下面msg
的值,可是当前执行的做用域是在父组件的实例上,经过this.msg
是获取不到子组件里面的值,咱们须要在<slot>
元素上动态绑定一个msg
对象属性,而后在父组件能够经过下面方式来获取
Vue.component('button-counter', {
data () {
return {
msg: 'hi'
}
},
template: '<div><slot :msg="msg">我是默认内容</slot></div>'
})
复制代码
new Vue({
el: '#app',
template: '<button-counter><template scope="msg"><span>{{props.msg}}</span></template></button-counter>'
})
复制代码
这是2.5之前的写法,2.5之后采用slot-scope
new Vue({
el: '#app',
template: '<button-counter><template slot-scope="msg"><span>{{props.msg}}</span></template></button-counter>'
})
复制代码
2.6之后废弃上面两种写法,采用v-slot
new Vue({
el: '#app',
template: '<button-counter><template v-slot="msg"><span>{{props.msg}}</span></template></button-counter>'
})
复制代码
这里可以拿到msg
是由于在renderSlot
的时候 执行会传入props
,能够看到编译后的render
函数_t
的第三个参数就是props
(function anonymous(
) {
with(this){return _c('div',[_t("default",[_v("我是默认的")],{"msg":msg})],2)}
})
复制代码
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
nodes = scopedSlotFn(props) || fallback;
return nodes;
复制代码
由render
函数能够看到传进去的props
后就可以拿到msg
的值了
(function anonymous(
) {
with(this){return _c('button-counter',{scopedSlots:_u([{key:"default",fn:function(props){return [_c('span',[_v(_s(props.msg))])]}}])})}
})
复制代码
这里只是简单描述了几个关键点,插槽做用域还有支持解构赋值,支持动态插槽名称等写法,还有不少的细节处理须要更加深刻去了解源码。
若是以为对你有帮忙,麻烦点个star哈 github地址