概述

在开发中,我们会经常封装一个个可复用的组件, 比如通过 props 传递给组件一些数据,让组件来进行展示

  • 但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的 div、 span 等等这些元素
  • 比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片
  • 应该让使用者可以决定某一块区域到底存放什么内容和元素

例如:

定制一个通用的导航组件 - NavBar

  • 这个组件分成三块区域: 左边-中间-右边,每块区域的内容是不固定
  • 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示
  • 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等
  • 右边可能是一个文字,也可能是一个图标,也可能什么都不显示

这个时候就可以来定义插槽 slot

  • 插槽的使用过程其实是抽取共性、预留不同
  • 共同的元素、内容依然在组件内进行封装
  • 同时会将不同的元素使用 slot 作为占位,让外部决定到底显示什么样的元素

定义

  • Vue 中将 <slot> 元素作为承载分发内容的出口
  • 在封装组件中,使用特殊的元素<slot>就可以为封装组件开启一个插槽
  • 该插槽插入什么内容取决于父组件如何使用

使用

ShowMessage. vue

// 两部分: 一个title, 一个内容
// 内容不固定,使用插槽定义
<template>
  <h2>{{ title }}</h2>
  <div class="content">
    <slot>
      <!-- 插槽内可以输入默认内容 -->
      <p>我是默认内容, 哈哈哈</p>
    </slot>
  </div>
</template>

<script>
  export default {
    props: {
      title: {
        type: String,
        default: "我是title默认值"
      }
    }
  }
</script>

<style scoped>
</style>

App. vue

// 插槽内容可自定义使用
<template>
  <div class="app">
    <!-- 1.内容是button -->
    <show-message title="哈哈哈">
      <button>我是按钮元素</button>
    </show-message>
    <!-- 2.内容是超链接 -->
    <show-message>
      <a href="#">百度一下</a>
    </show-message>
    <!-- 3.内容是一张图片 -->
    <show-message>
      <img src="@/img/kobe02.png" alt="">
    </show-message>
    <!-- 4.内容没有传递 -->
    <show-message></show-message>
  </div>
</template>

<script>
  import ShowMessage from './ShowMessage.vue'
  export default {
    components: {
      ShowMessage
    }
  }
</script>

<style scoped>
</style>

结果:

具名插槽

同时使用多个插槽时, 可以使用具名插槽

  • 具名插槽顾名思义就是给插槽起一个名字, <slot> 元素有一个特殊的 attribute: name
  • 一个不带 name 的 slot,会带有隐含的名字 default

动态插槽名

  • 可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称

代码演示:
NavBar.vue

<template>
  <div class="nav-bar">
    <div class="left">
      <slot name="left">left</slot>
    </div>
    <div class="center">
      <slot name="center">center</slot>
    </div>
    <div class="right">
      <slot name="right">right</slot>
    </div>
  </div>
  <!-- 插槽默认名 default -->
  <div class="other">
    <slot name="default"></slot>
  </div>
</template>
<script>
  export default {
  }
</script>
<style scoped>
  .nav-bar {
    display: flex;
    height: 44px;
    line-height: 44px;
    text-align: center;
  }
  .left {
    width: 80px;
    background-color: orange;
  }
  .center {
    flex: 1;
    background-color: skyblue;
  }
  .right {
    width: 80px;
    background-color: aquamarine;
  }
</style>

App.vue

<template>
  <!-- 具名插槽使用 -->
  <nav-bar>
<!-- v-slot: 缩写 # -->
    <template #left>
      <button>{{ leftText }}</button>
    </template>
    <template #center>
      <span>内容</span>
    </template>
    <template v-slot:right>
      <a href="#">登录</a>
    </template>
  </nav-bar>
  <hr>
  <!-- 动态插槽 -->
  <!-- nav-bar只给一个插槽传入数据 -->
  <nav-bar>
    <template v-slot:[position]>
      <a href="#">注册</a>
    </template>
  </nav-bar>
  <!-- 动态修改插槽位置 -->
  <button @click=" position = 'left' ">左边</button>
  <button @click=" position = 'center' ">中间</button>
  <button @click=" position = 'right' ">右边</button>
</template>
<script>
  import NavBar from './NavBar.vue'
  export default {
    components: {
      NavBar
    },
    data() {
      return {
        position: "center",
        leftText: "返回"
      }
    }
  }
</script>
<style scoped>
</style>

渲染作用域

  • 父级模板里的所有内容都是在父级作用域中编译的
  • 子模板里的所有内容都是在子作用域中编译的

如上面案例:
子组件中模板语法 就是获取子组件 data 中的,
即便该子组件最终会渲染到父组件 App.vue 中

作用域插槽

但是若想让插槽可以访问到子组件中的内容

案例如下:

  1. 在 App. vue 中定义好数据
  2. 传递给 ShowNames 组件中
  3. ShowNames 组件中遍历 names 数据
  4. 定义插槽的 prop
  5. 通过 v-slot: default 的方式获取到 slot 的 props
  6. 使用 slotProps 中的 item 和 index

独占默认插槽的缩写

  • 如果我们的插槽是默认插槽 default,那么在使用的时候 v-slot:default=”slotProps”可以简写为 v-slot=”slotProps”
  • 并且如果我们的插槽只有默认插槽时,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直接用在组件上

默认插槽和具名插槽混合

  • 如果我们有默认插槽和具名插槽,那么按照完整的 template 来编写
  • 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法