Vue对于插槽有两个专门的APIvm.$slotsvm.$scopedSlots,分别是普通插槽和作用域插槽,使用JSX语法或渲染函数的时候,定义插槽将使用上述两个API。

渲染函数createElement

普通插槽和作用域插槽在定义上相差不大,但是在使用方法上略微有点区别,详见渲染函数>数据对象

普通插槽,插槽内容以children子节点形式,然后在数据对象中指定插槽名

// 定义带插槽的组件,`$slots.default`为匿名插槽,其余的则是具名插槽,匿名插槽的插槽名可以省略
const MySlot = {
  render (h) {
    return h('div', [
      h('header', [this.$slots.header]),
      h('main', [this.$slots.header]),
      h('footer', [this.$slots.footer])
    ])
  }
}

// 在`children子节点`中指定插槽名以使用具名插槽,未指定插槽名的则放入匿名插槽中
export default {
  components: { MySlot },
  render (h) {
    return h('MySlot', [
      h('template', { slot: 'header' }, 'hello world'),
      'children node',
      h('div', { slot: 'footer' }, 'this is footer')
    ])
  }
}

作用域插槽,与普通插槽不同,作用域插槽的内容直接放入渲染函数的数据对象中的

// 定义作用域插槽
const MySlot = {
  data () {
    return { user: 'John', content: 'vue', copytight: 'CopyRight' }
  },
  render (h) {
    return h('div', [
      h('header', [this.$scopedSlots.header({ user: this.user })]),
      h('main', [this.$scopedSlots.default({ content: this.content })]),
      h('footer', [this.$scopedSlots.footer({ copytight: this.copytight })])
    ])
  }
}

// 要使用作用域插槽的数据内容,则插槽必须在组件的数据对象`scopedSlots`中使用,如`header`所示
// 作用域插槽也可以当作普通插槽使用,如`default`和`footer`
export default {
  components: { MySlot },
  render (h) {
    return h('MySlot', {
      scopedSlots: {
        header: props => `hello, ${props.user}`
      }
    }, [
      'children node',
      h('div', { slot: 'footer' }, 'this is footer')
    ])
  }
}

关于静态插槽和作用域插槽:

你可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组
也可以通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数

this.$slots 返回的是数组,this.$scopedSlots 返回的是函数,这里踩一个坑,使用 this.$scopedSlots 定义的插槽如果未被使用则会报错

例如删除或注释掉具名插槽footer内容后,控制台报错

[Vue warn]: Error in render: "TypeError: this.$scopedSlots.footer is not a function"

如何避免这个问题暂时没有找到解决方案,研究还不够深入


2021年1月25日补充:

使用 this.$scopedSlots 定义的插槽如果未被使用则会报错

原因: 以footer插槽为例,其实不用想的太复杂,作用域插槽this.$scopedSlots.footer()就是一个函数,MySlot组件使用了这个函数,但是父组件却没有传入此函数的定义,所以MySlot再调用这个函数的时候发生报错。
解决办法:MySlot调用this.$scopedSlots.footer()前进行一次判断此函数是否存在即可。


JSX语法

JSX是createElement的语法糖,在用法上没有什么区别,对照着上面的内容稍微改一改就好了

静态插槽

const MySlot = {
  render (h) {
    return (
      <div>
        <header>{this.$slots.header}</header>
        <main>{this.$slots.default}</main>
        <footer>{this.$slots.footer}</footer>
      </div>
    )
  }
}

export default {
  render (h) {
    return (
      <MySlot>
        <template slot='header'>hello world</template>
        children node
        <div slot='footer'>this is footer</div>
      </MySlot>
    )
  }
}

作用域插槽

const MySlot = {
  data () {
    return { user: 'John', content: 'vue', copytight: 'CopyRight' }
  },
  render (h) {
    return (
      <div>
        <header>{this.$scopedSlots.header({ user: this.user })}</header>
        <main>{this.$scopedSlots.default({ content: this.content })}</main>
        <footer>{this.$scopedSlots.footer({ copytight: this.copytight })}</footer>
      </div>
    )
  }
}

export default {
  render (h) {
    return (
      <MySlot
        scopedSlots={{
          header: props => `hello, ${props.user}`
        }}
      >
        children node
        <div slot='footer'>this is footer</div>
      </MySlot>
    )
  }
}
Logo

前往低代码交流专区

更多推荐