vue-slot插槽小记

553 阅读2分钟

插槽-slot

Slot在组件模板中占位,当使用该组件时,组件里的内容会替换组件模板中slot的位置,作为承载分发内容的出口 插槽可以让用户更好地扩展组件,更好地复用组件以及做一些定制化的处理

例如父组件在使用到一个复用组件的时候,不同场景下这个组件有少量表现不一样,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,让这个复用组件能在不同场景下适用 如:布局组件、表格列、下拉选、弹框显示内容等

1.slot使用

  • 默认slot使用 子组件里slot标签确认渲染的位置,标签里可以放一些dom结构,当父组件使用时,如果没用往插槽中传入内容,slot标签内的dom结构就会显示页面

  • 具名slot使用 子组件用v-slot来表示插槽的名字,不传则使用默认插槽 父组件中使用时在默认插槽的基础上加上slot属性,值为子组件插槽v-slot的属性值

父: image.png

子: image.png

页面上显示的顺序由父来决定,也就是说在子中把header放在最后面,最终在页面上也是head-content-bottom的顺序

  • 作用域slot使用

组件间传递数据

2.slot实现原理

slot本质上是返回VNode的函数,一般情况下,Vue中的组件要渲染到页面上需要经过template => render function => VNode => DOM

比如一个带slot的组件

Vue.component('button-counter', {
  template: '<div> <slot>我是默认内容</slot></div>'
})

new Vue({
    el: '#app',
    template: '<button-counter><span>我是slot传入内容</span></button-counter>',
    components:{buttonCounter}
})

经过vue编译, 组件渲染函数会变成这样

(function anonymous(
) {
with(this){return _c('div',[_t("default",[_v("我是默认内容")])],2)}
})

而这个_t就是slot渲染函数:

function renderSlot (
  name,
  fallback,
  props,
  bindObject
) {
  // 得到渲染插槽内容的函数    
  var scopedSlotFn = this.$scopedSlots[name];
  var nodes;
  // 如果存在插槽渲染函数,则执行插槽渲染函数,生成nodes节点返回
  // 否则使用默认值
  nodes = scopedSlotFn(props) || fallback;
  return nodes;
}

而scopedSlots其实就是递归解析各个节点, 获取slot

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
}