插槽 #

一、插槽概述 #

1.1 什么是插槽? #

插槽(Slots)是组件的内容分发机制,允许父组件向子组件传递模板内容。

text
┌─────────────────────────────────────────────────────┐
│                   插槽工作原理                       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  父组件                                             │
│  ┌─────────────────────────────────┐               │
│  │ <Card>                          │               │
│  │   <h2>标题</h2>     ───────┐    │               │
│  │   <p>内容</p>          │    │               │
│  │ </Card>               │    │               │
│  └─────────────────────────────────┘               │
│                              │                      │
│                              ▼                      │
│  子组件                                             │
│  ┌─────────────────────────────────┐               │
│  │ <div class="card">              │               │
│  │   <slot />  ◄───────────────────┘               │
│  │ </div>                          │               │
│  └─────────────────────────────────┘               │
│                                                     │
└─────────────────────────────────────────────────────┘

1.2 插槽的作用 #

作用 说明
内容分发 将内容传递到组件内部
灵活组合 组件可接收任意内容
模板复用 创建可复用的布局组件
组件通信 父子组件内容传递

二、默认插槽 #

2.1 基本用法 #

astro
---
// src/components/Card.astro
---

<div class="card">
  <slot />
</div>

<style>
  .card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    padding: 1.5rem;
  }
</style>

使用组件:

astro
---
import Card from '../components/Card.astro';
---

<Card>
  <h2>卡片标题</h2>
  <p>这是卡片的内容。</p>
</Card>

输出结果:

html
<div class="card">
  <h2>卡片标题</h2>
  <p>这是卡片的内容。</p>
</div>

2.2 布局组件示例 #

astro
---
// src/layouts/Layout.astro
---

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>My Site</title>
  </head>
  <body>
    <slot />
  </body>
</html>

使用布局:

astro
---
import Layout from '../layouts/Layout.astro';
---

<Layout>
  <h1>欢迎</h1>
  <p>这是页面内容。</p>
</Layout>

2.3 容器组件 #

astro
---
// src/components/Container.astro
interface Props {
  maxWidth?: string;
}

const { maxWidth = '1200px' } = Astro.props;
---

<div class="container" style={`max-width: ${maxWidth}`}>
  <slot />
</div>

<style>
  .container {
    margin: 0 auto;
    padding: 0 1rem;
  }
</style>

使用:

astro
---
import Container from '../components/Container.astro';
---

<Container>
  <p>内容居中显示</p>
</Container>

<Container maxWidth="800px">
  <p>较窄的容器</p>
</Container>

三、具名插槽 #

3.1 定义具名插槽 #

使用 name 属性定义具名插槽:

astro
---
// src/components/Card.astro
---

<div class="card">
  <div class="card-header">
    <slot name="header" />
  </div>
  
  <div class="card-body">
    <slot />
  </div>
  
  <div class="card-footer">
    <slot name="footer" />
  </div>
</div>

<style>
  .card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
  }
  
  .card-header {
    background: #f9fafb;
    padding: 1rem;
    border-bottom: 1px solid #e5e7eb;
  }
  
  .card-body {
    padding: 1.5rem;
  }
  
  .card-footer {
    background: #f9fafb;
    padding: 1rem;
    border-top: 1px solid #e5e7eb;
  }
</style>

3.2 使用具名插槽 #

使用 slot 属性指定插槽:

astro
---
import Card from '../components/Card.astro';
---

<Card>
  <h2 slot="header">卡片标题</h2>
  
  <p>这是卡片的主要内容。</p>
  <p>可以包含多个元素。</p>
  
  <div slot="footer">
    <button>确定</button>
    <button>取消</button>
  </div>
</Card>

3.3 模态框组件 #

astro
---
// src/components/Modal.astro
interface Props {
  open?: boolean;
}

const { open = false } = Astro.props;
---

<div class:list={['modal', { open }]}>
  <div class="modal-backdrop"></div>
  <div class="modal-content">
    <div class="modal-header">
      <slot name="header" />
      <button class="close-btn">&times;</button>
    </div>
    
    <div class="modal-body">
      <slot />
    </div>
    
    <div class="modal-footer">
      <slot name="footer" />
    </div>
  </div>
</div>

<style>
  .modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1000;
  }
  
  .modal.open {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  .modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
  }
  
  .modal-content {
    position: relative;
    background: white;
    border-radius: 8px;
    max-width: 500px;
    width: 90%;
  }
  
  .modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    border-bottom: 1px solid #e5e7eb;
  }
  
  .modal-body {
    padding: 1.5rem;
  }
  
  .modal-footer {
    padding: 1rem;
    border-top: 1px solid #e5e7eb;
  }
</style>

使用模态框:

astro
---
import Modal from '../components/Modal.astro';
---

<Modal open={true}>
  <h2 slot="header">确认操作</h2>
  
  <p>您确定要执行此操作吗?此操作无法撤销。</p>
  
  <div slot="footer">
    <button>取消</button>
    <button class="primary">确认</button>
  </div>
</Modal>

四、插槽默认内容 #

4.1 基本用法 #

<slot> 标签内放置默认内容:

astro
---
// src/components/Button.astro
---

<button class="btn">
  <slot>点击</slot>
</button>

<style>
  .btn {
    padding: 0.5rem 1rem;
    background: #2563eb;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

使用:

astro
---
import Button from '../components/Button.astro';
---

<!-- 使用默认内容 -->
<Button />

<!-- 提供自定义内容 -->
<Button>提交</Button>
<Button>保存更改</Button>

4.2 具名插槽默认内容 #

astro
---
// src/components/Article.astro
---

<article class="article">
  <header class="article-header">
    <slot name="header">
      <h1>默认标题</h1>
    </slot>
  </header>
  
  <div class="article-content">
    <slot>
      <p>暂无内容</p>
    </slot>
  </div>
  
  <footer class="article-footer">
    <slot name="footer">
      <p>作者:匿名</p>
    </slot>
  </footer>
</article>

五、检查插槽内容 #

5.1 Astro.slots.has() #

检查插槽是否有内容:

astro
---
// src/components/Card.astro
const hasHeader = Astro.slots.has('header');
const hasFooter = Astro.slots.has('footer');
---

<div class="card">
  {hasHeader && (
    <div class="card-header">
      <slot name="header" />
    </div>
  )}
  
  <div class="card-body">
    <slot />
  </div>
  
  {hasFooter && (
    <div class="card-footer">
      <slot name="footer" />
    </div>
  )}
</div>

5.2 条件渲染示例 #

astro
---
// src/components/Panel.astro
const hasTitle = Astro.slots.has('title');
const hasActions = Astro.slots.has('actions');
---

<div class="panel">
  {hasTitle && (
    <div class="panel-title">
      <slot name="title" />
    </div>
  )}
  
  <div class="panel-content">
    <slot />
  </div>
  
  {hasActions && (
    <div class="panel-actions">
      <slot name="actions" />
    </div>
  )}
</div>

<style>
  .panel {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
  }
  
  .panel-title {
    padding: 1rem;
    border-bottom: 1px solid #e5e7eb;
    font-weight: bold;
  }
  
  .panel-content {
    padding: 1.5rem;
  }
  
  .panel-actions {
    padding: 1rem;
    border-top: 1px solid #e5e7eb;
    display: flex;
    gap: 0.5rem;
  }
</style>

六、高级用法 #

6.1 嵌套插槽 #

astro
---
// src/components/Layout.astro
---

<div class="layout">
  <slot />
</div>

---
// src/components/PageLayout.astro
import Layout from './Layout.astro';
---

<Layout>
  <header>
    <slot name="header" />
  </header>
  
  <main>
    <slot />
  </main>
  
  <footer>
    <slot name="footer" />
  </footer>
</Layout>

6.2 插槽传递 #

astro
---
// src/components/List.astro
---

<ul class="list">
  <slot />
</ul>

---
// src/components/ListItem.astro
---

<li class="list-item">
  <slot />
</li>

---
// 使用
import List from './List.astro';
import ListItem from './ListItem.astro';
---

<List>
  <ListItem>项目一</ListItem>
  <ListItem>项目二</ListItem>
  <ListItem>项目三</ListItem>
</List>

6.3 动态插槽名 #

astro
---
// src/components/DynamicSlot.astro
interface Props {
  slotName: string;
}

const { slotName } = Astro.props;
---

<div>
  <slot name={slotName} />
</div>

七、实际应用示例 #

7.1 页面布局 #

astro
---
// src/layouts/MainLayout.astro
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import Sidebar from '../components/Sidebar.astro';

interface Props {
  title: string;
}

const { title } = Astro.props;
const hasSidebar = Astro.slots.has('sidebar');
---

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>{title}</title>
  </head>
  <body>
    <Header />
    
    <div class:list={['main', { 'has-sidebar': hasSidebar }]}>
      {hasSidebar && (
        <aside class="sidebar">
          <slot name="sidebar" />
        </aside>
      )}
      
      <main class="content">
        <slot />
      </main>
    </div>
    
    <Footer />
  </body>
</html>

<style>
  .main {
    display: flex;
    min-height: calc(100vh - 120px);
  }
  
  .sidebar {
    width: 250px;
    border-right: 1px solid #e5e7eb;
  }
  
  .content {
    flex: 1;
    padding: 2rem;
  }
</style>

使用:

astro
---
import MainLayout from '../layouts/MainLayout.astro';
---

<MainLayout title="首页">
  <nav slot="sidebar">
    <a href="/">首页</a>
    <a href="/blog">博客</a>
  </nav>
  
  <h1>欢迎</h1>
  <p>这是页面内容。</p>
</MainLayout>

7.2 表单组件 #

astro
---
// src/components/Form.astro
---

<form class="form">
  <slot name="header" />
  
  <div class="form-fields">
    <slot />
  </div>
  
  <div class="form-actions">
    <slot name="actions">
      <button type="submit">提交</button>
    </slot>
  </div>
</form>

<style>
  .form {
    max-width: 500px;
  }
  
  .form-fields {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  
  .form-actions {
    margin-top: 1.5rem;
  }
</style>

使用:

astro
---
import Form from '../components/Form.astro';
import FormField from '../components/FormField.astro';
---

<Form>
  <h2 slot="header">用户注册</h2>
  
  <FormField label="用户名" name="username" />
  <FormField label="邮箱" name="email" type="email" />
  <FormField label="密码" name="password" type="password" />
  
  <div slot="actions">
    <button type="submit">注册</button>
    <button type="button">取消</button>
  </div>
</Form>

7.3 数据表格 #

astro
---
// src/components/DataTable.astro
interface Props {
  columns: string[];
}

const { columns } = Astro.props;
---

<div class="data-table-wrapper">
  <slot name="toolbar" />
  
  <table class="data-table">
    <thead>
      <tr>
        {columns.map(col => (
          <th key={col}>{col}</th>
        ))}
      </tr>
    </thead>
    <tbody>
      <slot />
    </tbody>
  </table>
  
  <slot name="pagination" />
</div>

<style>
  .data-table-wrapper {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
  }
  
  .data-table {
    width: 100%;
    border-collapse: collapse;
  }
  
  th, td {
    padding: 0.75rem 1rem;
    text-align: left;
    border-bottom: 1px solid #e5e7eb;
  }
  
  th {
    background: #f9fafb;
    font-weight: 600;
  }
</style>

八、最佳实践 #

8.1 插槽命名规范 #

text
✅ 好的命名:
- header
- footer
- sidebar
- actions
- content

❌ 不好的命名:
- slot1
- s
- mySlot

8.2 提供合理的默认值 #

astro
---
// ✅ 好的做法:提供有意义的默认内容
---

<div class="card">
  <slot name="header">
    <h2>未命名卡片</h2>
  </slot>
  
  <slot>
    <p class="empty">暂无内容</p>
  </slot>
</div>

8.3 文档注释 #

astro
---
/**
 * Card 组件
 * 
 * @slot header - 卡片头部
 * @slot default - 卡片内容
 * @slot footer - 卡片底部
 * @slot actions - 操作按钮区域
 */
---

<div class="card">
  <slot name="header" />
  <slot />
  <slot name="footer" />
  <slot name="actions" />
</div>

九、总结 #

插槽核心要点:

text
┌─────────────────────────────────────────────────────┐
│                   插槽要点                           │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📦 默认插槽   <slot /> 接收内容                    │
│                                                     │
│  🏷️ 具名插槽   <slot name="xxx" /> 分类内容        │
│                                                     │
│  📝 默认内容   <slot>默认内容</slot>                │
│                                                     │
│  🔍 检查插槽   Astro.slots.has() 检查是否有内容     │
│                                                     │
│  🔄 插槽传递   嵌套组件间传递插槽                   │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 数据获取基础,掌握在 Astro 中获取数据的方法!

最后更新:2026-03-28