Skip to content

33.Vue 中 slot 是如何实现的?什么时候使用它?

1.1 什么是插槽?

插槽设计来源于 Web Components 规范草案,利用slot进行占位,在使用组件时,组件标签内部内容会分发到对应的 slot 中。

1.2 什么时候使用它

通过插槽可以让用户更好的对组件进行扩展和定制化。可以通过具名插槽指定渲染的位置。常用的组件例如:弹框组件、布局组件、表格组件、树组件......

1.3 插槽的分类和原理

具名插槽,作用域插槽

1)Vue2 插槽实现

  • 普通插槽的实现

    vue
    <template>
      <my>
        <h1 slot="title">标题</h1>
        <div slot="content">内容</div>
      </my>
    </template>
    <!-- 
        编译后的结果 
        with(this){ 
          return _c('my',[
            _c('h1',{attrs:{"slot":"title"},slot:"title"},[_v("标题")])
            _c('div',{attrs:{"slot":"content"},slot:"content"},[_v("内容")]) 
          ]) 
        } 
    -->
    <template>
      <my>
        <h1 slot="title">标题</h1>
        <div slot="content">内容</div>
      </my>
    </template>
    <!-- 
        编译后的结果 
        with(this){ 
          return _c('my',[
            _c('h1',{attrs:{"slot":"title"},slot:"title"},[_v("标题")])
            _c('div',{attrs:{"slot":"content"},slot:"content"},[_v("内容")]) 
          ]) 
        } 
    -->

    my.vue

    vue
    <template>
      <div>
        <slot name="title"></slot>
        <slot name="content"></slot>
      </div>
    </template>
    <!--
      编译后的结果
      with(this){
        return _c('div',[
          _t("title"),_t("content")
        ])
      }
    -->
    <template>
      <div>
        <slot name="title"></slot>
        <slot name="content"></slot>
      </div>
    </template>
    <!--
      编译后的结果
      with(this){
        return _c('div',[
          _t("title"),_t("content")
        ])
      }
    -->
  • 作用域插槽

    vue
    <template>
      <my>
        <template v-slot="{ article }">
          <h1>{{ article.title }}</h1>
          <div>{{ article.content }}</div>
        </template>
      </my>
    </template>
    <!--
      with(this) { 
        return _c('my', { 
          scopedSlots: _u([
            { 
              key: "default", 
              fn: function ({ article }) { 
                return [_c('h1', [_v(_s(article.title))]), _c('div',[ _v(_s(article.content)) ])] 
              } 
            }
          ]) 
        }) 
      }
    -->
    <template>
      <my>
        <template v-slot="{ article }">
          <h1>{{ article.title }}</h1>
          <div>{{ article.content }}</div>
        </template>
      </my>
    </template>
    <!--
      with(this) { 
        return _c('my', { 
          scopedSlots: _u([
            { 
              key: "default", 
              fn: function ({ article }) { 
                return [_c('h1', [_v(_s(article.title))]), _c('div',[ _v(_s(article.content)) ])] 
              } 
            }
          ]) 
        }) 
      }
    -->
    vue
    <template>
      <div>
        <slot :article="{ title: '标题', content: '内容' }"></slot>
      </div>
    </template>
    <!--
      编译后的结果
      with(this){
        return _c('div',[_t("default",null,{"article":{title:'标题',content:'内容'}})])
      }
    -->
    <template>
      <div>
        <slot :article="{ title: '标题', content: '内容' }"></slot>
      </div>
    </template>
    <!--
      编译后的结果
      with(this){
        return _c('div',[_t("default",null,{"article":{title:'标题',content:'内容'}})])
      }
    -->

普通插槽,渲染在父级, 作用域插槽在组件内部渲染!

2)Vue3 插槽实现

vue
<template>
  <my>
    <template #default="{ article }">
      <h1>{{ article.title }}</h1>
      <div>{{ article.content }}</div>
    </template>
  </my>
</template>
<!--
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_my = _resolveComponent("my")
  return (_openBlock(), _createBlock(_component_my, null, {
    default: _withCtx(({ article }) => [
      _createElementVNode("h1", null, _toDisplayString(article.title), 1 /* TEXT */),
      _createElementVNode("div", null, _toDisplayString(article.content), 1 /* TEXT */)
    ]),
  }))
}
-->
<template>
  <my>
    <template #default="{ article }">
      <h1>{{ article.title }}</h1>
      <div>{{ article.content }}</div>
    </template>
  </my>
</template>
<!--
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_my = _resolveComponent("my")
  return (_openBlock(), _createBlock(_component_my, null, {
    default: _withCtx(({ article }) => [
      _createElementVNode("h1", null, _toDisplayString(article.title), 1 /* TEXT */),
      _createElementVNode("div", null, _toDisplayString(article.content), 1 /* TEXT */)
    ]),
  }))
}
-->
vue
<template>
  <div>
    <slot :article="{ title: '标题', content: '内容' }"></slot>
  </div>
</template>
<!--
  export function render(_ctx, _cache, $props, $setup, $data, $options) {
    return (_openBlock(), _createElementBlock("div", null, [
      _renderSlot(_ctx.$slots, "default", { article: { title: '标题', content: '内容' } })
    ]))
  }
-->
<template>
  <div>
    <slot :article="{ title: '标题', content: '内容' }"></slot>
  </div>
</template>
<!--
  export function render(_ctx, _cache, $props, $setup, $data, $options) {
    return (_openBlock(), _createElementBlock("div", null, [
      _renderSlot(_ctx.$slots, "default", { article: { title: '标题', content: '内容' } })
    ]))
  }
-->

Released under the MIT License.