10.4 作用域插槽
最后说说作用域插槽,我们可以利用作用域插槽让父组件的插槽内容访问到子组件的数据,具体的用法是在子组件中以属性的方式记录在子组件中,父组件通过v-slot:[name]=[props]
的形式拿到子组件传递的值。子组件<slot>
元素上的特性称为插槽Props
,另外,vue2.6以后的版本已经弃用了slot-scoped
,采用v-slot
代替。
var child = {
template: `<div><slot :user="user"></div>`,
data() {
return {
user: {
firstname: 'test'
}
}
}
}
var vm = new Vue({
el: '#app',
components: {
child
},
template: `<div id="app"><child><template v-slot:default="slotProps">{{slotProps.user.firstname}}</template></child></div>`
})
作用域插槽和具名插槽的原理类似,我们接着往下看。
10.4.1 父组件编译阶段
作用域插槽和具名插槽在父组件的用法基本相同,区别在于v-slot
定义了一个插槽props
的名字,参考对于具名插槽的分析,生成render
函数阶段fn
函数会携带props
参数传入。即:
with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',{scopedSlots:_u([{key:"default",fn:function(slotProps){return [_v(_s(slotProps.user.firstname))]}}])})],1)}
10.4.2 子组件渲染
在子组件编译阶段,:user="user"
会以属性的形式解析,最终在render
函数生成阶段以对象参数的形式传递_t
函数。
with(this){return _c('div',[_t("default",null,{"user":user})],2)}
子组件渲染Vnode阶段,根据前面分析会执行renderSlot
函数,这个函数前面分析过,对于作用域插槽的处理,集中体现在函数传入的第三个参数。
// 渲染slot组件vnode
function renderSlot(
name,
fallback,
props, // 子传给父的值 { user: user }
bindObject
) {
// scopedSlotFn拿到父组件插槽的执行函数,默认slotname为default
var scopedSlotFn = this.$scopedSlots[name];
var nodes;
// 具名插槽分支
if (scopedSlotFn) { // scoped slot
props = props || {};
if (bindObject) {
if (!isObject(bindObject)) {
warn(
'slot v-bind without argument expects an Object',
this
);
}
// 合并props
props = extend(extend({}, bindObject), props);
}
// 执行时将子组件传递给父组件的值传入fn
nodes = scopedSlotFn(props) || fallback;
}
最终将子组件的插槽props
作为参数传递给执行函数执行。回过头看看为什么具名插槽是函数的形式执行而不是直接返回结果。学完作用域插槽我们发现这就是设计巧妙的地方,函数的形式让执行过程更加灵活,作用域插槽只需要以参数的形式将插槽props
传入便可以得到想要的结果。
10.4.3 思考
作用域插槽这个概念一开始我很难理解,单纯从定义和源码的结论上看,父组件的插槽内容可以访问到子组件的数据,这不是明显的子父之间的信息通信吗,在事件章节我们知道,子父组件之间的通信完全可以通过事件$emit,$on
的形式来完成,那么为什么还需要增加一个插槽props
的概念呢。我们看看作者的解释。
插槽
prop
允许我们将插槽转换为可复用的模板,这些模板可以基于输入的prop
渲染出不同的内容
从我自身的角度理解,作用域插槽提供了一种方式,当你需要封装一个通用,可复用的逻辑模块,并且这个模块给外部使用者提供了一个便利,允许你在使用组件时自定义部分布局,这时候作用域插槽就派上大用场了,再到具体的思想,我们可以看看几个工具库Vue Virtual Scroller, Vue Promised对这一思想的应用。