插槽

什么是插槽?

插槽(Slot)是 Vue 组件中的一个重要概念,它允许父组件向子组件传递 HTML 内容,实现组件的内容分发。

1. 默认插槽

默认插槽是最基本的插槽类型,允许父组件向子组件传递任意内容。

子组件(定义插槽)

<template>
  <div class="card">
    <div class="card-header">
      <h3>卡片标题</h3>
    </div>
    <div class="card-body">
      <!-- 默认插槽 -->
      <slot>默认内容</slot>
    </div>
    <div class="card-footer">
      卡片底部
    </div>
  </div>
</template>

父组件(使用插槽)

<template>
  <div>
    <CardComponent>
      <p>这是卡片的自定义内容</p>
      <button @click="handleClick">点击按钮</button>
    </CardComponent>
    
    <!-- 不提供内容,使用默认值 -->
    <CardComponent></CardComponent>
  </div>
</template>

2. 具名插槽

具名插槽允许父组件向子组件的不同位置传递内容,通过名称来区分。

子组件(定义多个具名插槽)

<template>
  <div class="layout">
    <header class="header">
      <!-- 具名插槽:header -->
      <slot name="header">默认头部</slot>
    </header>
    <main class="main">
      <!-- 默认插槽,等同于 name="default" -->
      <slot>默认内容</slot>
    </main>
    <aside class="sidebar">
      <!-- 具名插槽:sidebar -->
      <slot name="sidebar">默认侧边栏</slot>
    </aside>
    <footer class="footer">
      <!-- 具名插槽:footer -->
      <slot name="footer">默认底部</slot>
    </footer>
  </div>
</template>

父组件(使用具名插槽)

<template>
  <LayoutComponent>
    <!-- 使用 v-slot 指令指定插槽名称 -->
    <template v-slot:header>
      <h1>页面标题</h1>
    </template>
    
    <!-- 默认插槽内容 -->
    <p>这是页面的主要内容</p>
    <p>可以包含多个元素</p>
    
    <!-- 简写语法:#sidebar -->
    <template #sidebar>
      <ul>
        <li>菜单项 1</li>
        <li>菜单项 2</li>
        <li>菜单项 3</li>
      </ul>
    </template>
    
    <template #footer>
      <p>版权所有 © 2025</p>
    </template>
  </LayoutComponent>
</template>

3. 作用域插槽

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

子组件(向插槽传递数据)

<template>
  <div class="list">
    <h3>用户列表</h3>
    <ul>
      <li v-for="user in users" :key="user.id">
        <!-- 作用域插槽,传递 user 数据 -->
        <slot :user="user" :index="$index">
          {{ user.name }} - {{ user.email }}
        </slot>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, name: 'John', email: 'john@example.com' },
        { id: 2, name: 'Jane', email: 'jane@example.com' },
        { id: 3, name: 'Bob', email: 'bob@example.com' }
      ]
    }
  }
}
</script>

父组件(使用插槽数据)

<template>
  <UserListComponent>
    <!-- 使用 v-slot 接收插槽数据 -->
    <template v-slot:default="slotProps">
      <div class="user-item">
        <strong>{{ slotProps.index + 1 }}. {{ slotProps.user.name }}</strong>
        <span class="email">{{ slotProps.user.email }}</span>
      </div>
    </template>
  </UserListComponent>
  
  <!-- 解构插槽数据 -->
  <UserListComponent>
    <template #default="{ user, index }">
      <div class="user-item compact">
        {{ index + 1 }}. {{ user.name }}
      </div>
    </template>
  </UserListComponent>
</template>

4. 动态插槽名

Vue 3 允许使用动态插槽名,增加了插槽使用的灵活性。

父组件(使用动态插槽名)

<template>
  <ComponentWithSlots>
    <!-- 动态插槽名 -->
    <template v-slot:[dynamicSlotName]>
      <p>动态插槽内容</p>
    </template>
    
    <!-- 简写形式 -->
    <template #[dynamicSlotName]>
      <p>动态插槽内容</p>
    </template>
  </ComponentWithSlots>
</template>

<script>
export default {
  data() {
    return {
      dynamicSlotName: 'header'
    }
  }
}
</script>

5. 插槽的默认值

可以为插槽设置默认内容,当父组件没有提供插槽内容时,将显示默认内容。

子组件(设置插槽默认值)

<template>
  <div class="card">
    <div class="card-body">
      <slot>
        <p>这是默认内容</p>
        <button>默认按钮</button>
      </slot>
    </div>
  </div>
</template>

插槽的使用场景

  • 组件库开发,允许用户自定义组件内容
  • 布局组件,如 header、main、sidebar、footer 结构
  • 列表组件,允许用户自定义列表项的渲染方式
  • 卡片组件,允许用户自定义卡片内容
  • 模态框、对话框等组件,允许用户自定义内容

最佳实践

  • 为插槽提供有意义的默认内容
  • 使用具名插槽时,名称要清晰易懂
  • 作用域插槽传递的数据要精简,只传递必要的数据
  • 合理使用插槽,避免过度复杂的插槽结构
  • 在组合式 API 中,可以使用 useSlots 和 useAttrs 访问插槽和属性