第3章:组件化开发基础

第9节:插槽(Slots)系统

3.9.1 默认插槽与后备内容

插槽允许父组件向子组件的特定位置插入内容,使组件更加灵活和可复用。

默认插槽

默认插槽是最基本的插槽类型,子组件中使用<slot>标签定义插槽位置,父组件直接在子组件标签内插入内容:

<!-- 子组件 BaseLayout.vue -->
<template>
  <div class="container">
    <header>
      <h1>网站标题</h1>
    </header>
    <main>
      <!-- 默认插槽位置 -->
      <slot></slot>
    </main>
    <footer>
      <p>版权信息</p>
    </footer>
  </div>
</template>

父组件使用:

<!-- 父组件 App.vue -->
<template>
  <BaseLayout>
    <!-- 插入到默认插槽的内容 -->
    <h2>欢迎来到我的网站</h2>
    <p>这是网站的主要内容</p>
  </BaseLayout>
</template>

后备内容

后备内容是当父组件没有提供插槽内容时显示的默认内容:

<!-- 子组件 Button.vue -->
<template>
  <button class="btn">
    <!-- 带后备内容的插槽 -->
    <slot>默认按钮</slot>
  </button>
</template>

父组件使用:

<!-- 父组件 App.vue -->
<template>
  <!-- 使用默认内容 -->
  <Button></Button>
  
  <!-- 提供自定义内容 -->
  <Button>
    自定义按钮
  </Button>
</template>

3.9.2 具名插槽

当子组件需要多个插槽时,可以使用具名插槽。子组件中使用name属性为插槽命名,父组件使用v-slot指令(或简写#)指定插槽名称:

<!-- 子组件 BaseLayout.vue -->
<template>
  <div class="container">
    <header>
      <!-- 具名插槽:header -->
      <slot name="header"></slot>
    </header>
    <main>
      <!-- 默认插槽 -->
      <slot></slot>
    </main>
    <footer>
      <!-- 具名插槽:footer -->
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

父组件使用:

<!-- 父组件 App.vue -->
<template>
  <BaseLayout>
    <!-- 插入到header插槽 -->
    <template v-slot:header>
      <h1>网站标题</h1>
      <nav>
        <a href="/">首页</a>
        <a href="/about">关于</a>
      </nav>
    </template>
    
    <!-- 插入到默认插槽 -->
    <h2>欢迎来到我的网站</h2>
    <p>这是网站的主要内容</p>
    
    <!-- 插入到footer插槽 -->
    <template v-slot:footer>
      <p>© 2024 我的网站</p>
    </template>
  </BaseLayout>
</template>

具名插槽的简写

v-slot: 可以简写为 #

<template>
  <BaseLayout>
    <!-- 简写形式 -->
    <template #header>
      <h1>网站标题</h1>
    </template>
    
    <h2>欢迎来到我的网站</h2>
    <p>这是网站的主要内容</p>
    
    <template #footer>
      <p>© 2024 我的网站</p>
    </template>
  </BaseLayout>
</template>

3.9.3 作用域插槽

作用域插槽允许子组件向父组件传递数据,父组件可以使用这些数据来自定义插槽内容。

基本用法

子组件通过v-bind将数据传递给插槽,父组件使用v-slot指令接收数据:

<!-- 子组件 List.vue -->
<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      <!-- 将item和index传递给插槽 -->
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script setup>
const props = defineProps({
  items: {
    type: Array,
    default: () => []
  }
})
</script>

父组件使用:

<!-- 父组件 App.vue -->
<template>
  <List :items="items">
    <!-- 接收插槽传递的数据 -->
    <template v-slot="slotProps">
      {{ slotProps.index + 1 }}. {{ slotProps.item.name }}
    </template>
  </List>
</template>

<script setup>
const items = [
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' }
]
</script>

解构插槽参数

可以使用ES6解构语法简化插槽参数的使用:

<template>
  <List :items="items">
    <!-- 解构插槽参数 -->
    <template v-slot="{ item, index }">
      {{ index + 1 }}. {{ item.name }}
    </template>
  </List>
</template>

具名作用域插槽

具名插槽也可以传递数据:

<!-- 子组件 Table.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="column in columns" :key="column.key">
          <slot :name="`header-${column.key}`" :column="column">
            {{ column.title }}
          </slot>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in rows" :key="row.id">
        <td v-for="column in columns" :key="column.key">
          <slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
            {{ row[column.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

父组件使用:

<template>
  <Table :columns="columns" :rows="rows">
    <!-- 自定义特定列的表头 -->
    <template #header-name="{ column }">
      <strong>{{ column.title }}</strong>
    </template>
    
    <!-- 自定义特定列的单元格 -->
    <template #cell-actions="{ row }">
      <button @click="edit(row)">编辑</button>
      <button @click="delete(row)">删除</button>
    </template>
  </Table>
</template>

3.9.4 动态插槽名与v-slot缩写

动态插槽名

Vue 2.6+支持动态插槽名,可以使用方括号语法动态指定插槽名称:

<template>
  <BaseLayout>
    <!-- 动态插槽名 -->
    <template v-slot:[dynamicSlotName]>
      <h1>动态插槽内容</h1>
    </template>
  </BaseLayout>
</template>

<script setup>
import { ref } from 'vue'

const dynamicSlotName = ref('header')
</script>

v-slot缩写

v-slot: 可以简写为 #,但需要注意以下几点:

  1. 只有具名插槽才能使用简写
  2. 简写形式必须有参数
  3. 对于默认插槽,需要显式使用 #default
<template>
  <BaseLayout>
    <!-- 具名插槽简写 -->
    <template #header>
      <h1>网站标题</h1>
    </template>
    
    <!-- 默认插槽简写 -->
    <template #default>
      <p>网站内容</p>
    </template>
    
    <!-- 带参数的作用域插槽简写 -->
    <template #footer="{ footerData }">
      <p>{{ footerData.copyright }}</p>
    </template>
    
    <!-- 解构参数的作用域插槽简写 -->
    <template #footer="{ copyright }">
      <p>{{ copyright }}</p>
    </template>
  </BaseLayout>
</template>

插槽使用最佳实践

  1. 合理使用插槽类型:根据需求选择合适的插槽类型(默认插槽、具名插槽、作用域插槽)
  2. 提供后备内容:为插槽提供合理的后备内容,提高组件的易用性
  3. 保持插槽简单:插槽内容应该简洁,复杂逻辑建议在父组件中处理
  4. 使用语义化的插槽名称:插槽名称应该清晰反映其用途
  5. 适当使用作用域插槽:只有当需要子组件传递数据时才使用作用域插槽

总结

插槽是Vue组件化开发中的重要特性,它允许父组件向子组件插入自定义内容,使组件更加灵活和可复用。通过掌握默认插槽、具名插槽和作用域插槽的使用,你可以编写出更加通用和强大的Vue组件。

在下一章中,我们将学习Vue.js的响应式系统与生命周期。

« 上一篇 自定义事件与组件通信 下一篇 » 响应式原理深入