Vue高级用法插槽原理深入分析(下)

908 阅读2分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

作用域插槽

我们可以利用作用域插槽让父组件的插槽内容访问到子组件的数据,具体的用法是在子组件中以属性的方式记录在子组件中,父组件通过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>`
})

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

父组件编译阶段

作用域插槽和具名插槽在父组件的用法基本相同,区别在于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)}

子组件渲染

在子组件编译阶段,: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传入便可以得到想要的结果。