组件基础 #
一、什么是 Astro 组件? #
Astro 组件是 Astro 项目的基本构建块。每个 .astro 文件都是一个独立的组件,包含 HTML、CSS 和 JavaScript。
组件特点 #
text
┌─────────────────────────────────────────────────────┐
│ Astro 组件特点 │
├─────────────────────────────────────────────────────┤
│ │
│ 📦 独立封装 - HTML、CSS、JS 封装在一起 │
│ │
│ 🔄 可复用 - 在多个页面重复使用 │
│ │
│ 📥 可配置 - 通过属性传递数据 │
│ │
│ 🎨 样式隔离 - 组件样式默认作用域隔离 │
│ │
│ ⚡ 零 JS - 默认不发送 JavaScript 到客户端 │
│ │
└─────────────────────────────────────────────────────┘
二、组件结构 #
2.1 基本结构 #
一个 Astro 组件由两个主要部分组成:
astro
---
// 组件脚本(Frontmatter)
// 这里写 JavaScript/TypeScript 代码
const greeting = "Hello";
---
<!-- 组件模板 -->
<!-- 这里写 HTML 模板 -->
<h1>{greeting}, World!</h1>
2.2 完整组件示例 #
astro
---
// src/components/Card.astro
// 定义 Props 类型
interface Props {
title: string;
description: string;
image?: string;
}
// 解构获取属性
const { title, description, image = '/default.jpg' } = Astro.props;
// 可以在这里执行任意 JavaScript
const formattedTitle = title.toUpperCase();
---
<div class="card">
<img src={image} alt={title} class="card-image" />
<div class="card-content">
<h3 class="card-title">{formattedTitle}</h3>
<p class="card-description">{description}</p>
</div>
</div>
<style>
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-4px);
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-content {
padding: 1rem;
}
.card-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.card-description {
color: #6b7280;
line-height: 1.6;
}
</style>
<script>
// 客户端 JavaScript(打包后发送到客户端)
console.log('Card component loaded');
</script>
三、组件属性(Props) #
3.1 定义和接收属性 #
astro
---
// src/components/Button.astro
interface Props {
text: string;
type?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
onClick?: string;
}
const {
text,
type = 'primary',
disabled = false,
onClick
} = Astro.props;
---
<button
class={`btn btn-${type}`}
disabled={disabled}
onclick={onClick}
>
{text}
</button>
<style>
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: 500;
}
.btn-primary {
background: #2563eb;
color: white;
}
.btn-secondary {
background: #6b7280;
color: white;
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
3.2 使用组件 #
astro
---
import Button from '../components/Button.astro';
---
<!-- 基本使用 -->
<Button text="点击我" />
<!-- 带属性使用 -->
<Button text="提交" type="primary" />
<Button text="取消" type="secondary" />
<Button text="删除" type="danger" />
<!-- 禁用状态 -->
<Button text="不可点击" disabled={true} />
3.3 属性类型验证 #
使用 TypeScript 进行类型验证:
astro
---
// src/components/UserCard.astro
interface Props {
name: string;
age: number;
email: string;
avatar?: string;
role: 'admin' | 'user' | 'guest';
skills: string[];
metadata: {
createdAt: Date;
updatedAt: Date;
};
}
const {
name,
age,
email,
avatar,
role,
skills,
metadata
} = Astro.props;
---
<div class="user-card">
<img src={avatar || '/default-avatar.png'} alt={name} />
<h2>{name}</h2>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
<p>角色: {role}</p>
<div class="skills">
{skills.map(skill => <span class="skill">{skill}</span>)}
</div>
</div>
四、插槽(Slots) #
4.1 基本插槽 #
astro
---
// src/components/Container.astro
---
<div class="container">
<slot />
</div>
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
</style>
使用:
astro
---
import Container from '../components/Container.astro';
---
<Container>
<h1>标题</h1>
<p>内容</p>
</Container>
4.2 具名插槽 #
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>
使用:
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>
4.3 插槽默认内容 #
astro
---
// src/components/Alert.astro
---
<div class="alert">
<slot>
<p>这是默认内容,当没有提供内容时显示。</p>
</slot>
</div>
4.4 检查插槽内容 #
astro
---
// src/components/Modal.astro
const hasHeader = Astro.slots.has('header');
const hasFooter = Astro.slots.has('footer');
---
<div class="modal">
{hasHeader && (
<div class="modal-header">
<slot name="header" />
</div>
)}
<div class="modal-body">
<slot />
</div>
{hasFooter && (
<div class="modal-footer">
<slot name="footer" />
</div>
)}
</div>
五、组件样式 #
5.1 Scoped 样式(默认) #
组件内的 <style> 标签默认是作用域隔离的:
astro
---
// src/components/Button.astro
---
<button class="btn">
<slot />
</button>
<style>
/* 只影响当前组件的 .btn */
.btn {
padding: 0.5rem 1rem;
background: blue;
color: white;
}
</style>
5.2 全局样式 #
使用 :global() 或 is:global:
astro
---
// src/components/Button.astro
---
<button class="btn">
<slot />
</button>
<style>
.btn {
/* scoped 样式 */
}
/* 全局样式 */
:global(.btn-primary) {
background: blue;
}
</style>
<style is:global>
/* 整个样式块都是全局的 */
.btn-secondary {
background: gray;
}
</style>
5.3 CSS 变量 #
astro
---
// src/components/Button.astro
---
<button class="btn">
<slot />
</button>
<style>
.btn {
--btn-bg: #2563eb;
--btn-color: white;
--btn-padding: 0.5rem 1rem;
padding: var(--btn-padding);
background: var(--btn-bg);
color: var(--btn-color);
}
</style>
使用时覆盖变量:
astro
---
import Button from '../components/Button.astro';
---
<Button style="--btn-bg: #dc2626;">删除</Button>
5.4 条件样式 #
astro
---
// src/components/Button.astro
interface Props {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
}
const { variant = 'primary', size = 'md' } = Astro.props;
---
<button class={`btn btn-${variant} btn-${size}`}>
<slot />
</button>
<style>
.btn {
border: none;
cursor: pointer;
border-radius: 4px;
}
.btn-primary { background: #2563eb; color: white; }
.btn-secondary { background: #6b7280; color: white; }
.btn-danger { background: #dc2626; color: white; }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
.btn-md { padding: 0.5rem 1rem; font-size: 1rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1.125rem; }
</style>
六、组件组合 #
6.1 组件嵌套 #
astro
---
// src/components/Card.astro
import Button from './Button.astro';
interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<div class="card">
<h3>{title}</h3>
<p>{description}</p>
<Button>了解更多</Button>
</div>
6.2 组件作为属性 #
astro
---
// src/components/Layout.astro
import Header from './Header.astro';
import Footer from './Footer.astro';
interface Props {
title: string;
header?: any;
}
const { title, header = Header } = Astro.props;
---
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
{header()}
<main>
<slot />
</main>
<Footer />
</body>
</html>
七、组件最佳实践 #
7.1 单一职责 #
每个组件只做一件事:
text
✅ 好的做法:
├── Button.astro # 只负责按钮样式
├── Icon.astro # 只负责图标显示
└── Card.astro # 组合 Button 和 Icon
❌ 不好的做法:
└── ButtonWithIconAndCard.astro # 做太多事情
7.2 Props 设计 #
astro
---
// ✅ 好的 Props 设计
interface Props {
user: {
name: string;
email: string;
};
onEdit?: () => void;
}
// ❌ 不好的 Props 设计
interface Props {
userName: string;
userEmail: string;
editHandler: any;
}
7.3 组件命名 #
text
✅ 好的命名:
├── UserCard.astro
├── NavigationBar.astro
├── BlogPost.astro
└── SearchInput.astro
❌ 不好的命名:
├── card.astro
├── nav.astro
├── post.astro
└── input.astro
八、总结 #
Astro 组件核心要点:
text
┌─────────────────────────────────────────────────────┐
│ 组件核心要点 │
├─────────────────────────────────────────────────────┤
│ │
│ 📝 结构 Frontmatter + Template │
│ │
│ 📥 Props 类型定义 + 默认值 │
│ │
│ 🔲 Slots 内容分发 + 具名插槽 │
│ │
│ 🎨 Styles Scoped 默认 + 全局可选 │
│ │
│ 🔄 组合 组件嵌套 + 组件复用 │
│ │
└─────────────────────────────────────────────────────┘
下一步,让我们学习 页面与路由,掌握 Astro 的路由系统!
最后更新:2026-03-28