插槽 #
一、插槽概述 #
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">×</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