10.4 作用域插槽

最后说说作用域插槽,我们可以利用作用域插槽让父组件的插槽内容访问到子组件的数据,具体的用法是在子组件中以属性的方式记录在子组件中,父组件通过v-slot:[name]=[props]的形式拿到子组件传递的值。子组件<slot>元素上的特性称为插槽Props,另外,vue2.6以后的版本已经弃用了slot-scoped,采用v-slot代替。

  1. var child = {
  2. template: `<div><slot :user="user"></div>`,
  3. data() {
  4. return {
  5. user: {
  6. firstname: 'test'
  7. }
  8. }
  9. }
  10. }
  11. var vm = new Vue({
  12. el: '#app',
  13. components: {
  14. child
  15. },
  16. template: `<div id="app"><child><template v-slot:default="slotProps">{{slotProps.user.firstname}}</template></child></div>`
  17. })

作用域插槽和具名插槽的原理类似,我们接着往下看。

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函数,这个函数前面分析过,对于作用域插槽的处理,集中体现在函数传入的第三个参数。

  1. // 渲染slot组件vnode
  2. function renderSlot(
  3. name,
  4. fallback,
  5. props, // 子传给父的值 { user: user }
  6. bindObject
  7. ) {
  8. // scopedSlotFn拿到父组件插槽的执行函数,默认slotname为default
  9. var scopedSlotFn = this.$scopedSlots[name];
  10. var nodes;
  11. // 具名插槽分支
  12. if (scopedSlotFn) { // scoped slot
  13. props = props || {};
  14. if (bindObject) {
  15. if (!isObject(bindObject)) {
  16. warn(
  17. 'slot v-bind without argument expects an Object',
  18. this
  19. );
  20. }
  21. // 合并props
  22. props = extend(extend({}, bindObject), props);
  23. }
  24. // 执行时将子组件传递给父组件的值传入fn
  25. nodes = scopedSlotFn(props) || fallback;
  26. }

最终将子组件的插槽props作为参数传递给执行函数执行。回过头看看为什么具名插槽是函数的形式执行而不是直接返回结果。学完作用域插槽我们发现这就是设计巧妙的地方,函数的形式让执行过程更加灵活,作用域插槽只需要以参数的形式将插槽props传入便可以得到想要的结果。

10.4.3 思考

作用域插槽这个概念一开始我很难理解,单纯从定义和源码的结论上看,父组件的插槽内容可以访问到子组件的数据,这不是明显的子父之间的信息通信吗,在事件章节我们知道,子父组件之间的通信完全可以通过事件$emit,$on的形式来完成,那么为什么还需要增加一个插槽props的概念呢。我们看看作者的解释。

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容

从我自身的角度理解,作用域插槽提供了一种方式,当你需要封装一个通用,可复用的逻辑模块,并且这个模块给外部使用者提供了一个便利,允许你在使用组件时自定义部分布局,这时候作用域插槽就派上大用场了,再到具体的思想,我们可以看看几个工具库Vue Virtual ScrollerVue Promised对这一思想的应用。