Astro 组件的基本结构是什么?如何定义和使用 Props、插槽?
Astro 是近年来增长最快的前端框架之一,其组件系统融合了服务端逻辑与客户端模板的独特设计,让开发者可以用最少的 JavaScript 构建高性能页面。本文将系统讲解 Astro 组件的三大核心结构——前置脚本、模板区域和样式作用域,以及 Props 传参与 Slots 插槽的完整用法。
Astro 组件的三大结构
每个 .astro 文件都由三个可选部分组成:前置脚本(Frontmatter)、HTML 模板和 <style> 样式块。理解这三部分的执行时机和作用域,是掌握 Astro 组件的基础。
1. 前置脚本(Frontmatter)
用 --- 分隔符包裹的顶部区域,是组件的"服务端大脑":
astro--- // 这里的代码在构建时(或 SSR 请求时)执行,不会发送到浏览器 const title = "我的博客文章"; const date = new Date().toLocaleDateString(); // 支持导入其他组件 import Card from './Card.astro'; // 支持异步操作,如数据获取 const posts = await fetch('/api/posts').then(r => r.json()); ---
关键要点:
- 前置脚本中的代码仅在服务端执行,永远不会出现在客户端 bundle 中
- 可以使用完整的 JavaScript/TypeScript 语法,包括顶层
await - 这里定义的变量可以在下方模板中直接使用
- 无法访问浏览器 API(如
window、document)
2. 模板区域
紧跟在前置脚本之后的 HTML 区域,支持类 JSX 语法:
astro<h1>{title}</h1> <p>发布于 {date}</p> <div class="posts"> {posts.map(post => ( <Card title={post.title} /> ))} </div>
模板支持的表达式:
| 语法 | 用途 | 示例 |
|---|---|---|
{variable} | 变量插值 | <h1>{title}</h1> |
{condition && <Comp />} | 条件渲染 | {isAdmin && <AdminPanel />} |
{a ? <A /> : <B />} | 三元条件 | {loggedIn ? <Dashboard /> : <Login />} |
{items.map(...)} | 列表渲染 | {posts.map(p => <Card {...p} />)} |
set:html={raw} | 原始 HTML 注入 | <div set:html={content} /> |
3. 样式作用域
astro<style> /* 默认 scoped,不会影响其他组件 */ h1 { color: #333; } /* 需要全局样式时使用 :global() */ :global(.markdown-body p) { line-height: 1.8; } </style>
Astro 的样式默认是作用域隔离的——每个组件的样式会自动添加唯一属性选择器,杜绝样式泄漏。如果需要影响子组件或全局,使用 :global() 选择器。
Props:组件间的数据传递
Props 是 Astro 组件接收外部数据的标准方式,通过 Astro.props 对象访问。
基本用法
astro--- // Card.astro const { title, description } = Astro.props; --- <div class="card"> <h2>{title}</h2> <p>{description}</p> </div>
使用组件时传入 Props:
astro--- import Card from './Card.astro'; --- <Card title="文章标题" description="文章描述" />
TypeScript 类型约束
为 Props 添加类型定义,可以在构建时捕获错误:
astro--- interface Props { title: string; description?: string; // 可选属性 count?: number; } const { title, description = '暂无描述', count = 0 } = Astro.props satisfies Props; --- <h1>{title}</h1> <p>{description}</p> <span>数量: {count}</span>
使用 satisfies 操作符既能获得类型检查,又能保留解构时的默认值推断。
Props 传递的最佳实践
- 保持 Props 简单:Props 应该是序列化安全的原始数据(字符串、数字、布尔值、简单对象),避免传递函数或复杂类实例
- 提供默认值:通过解构默认值为可选 Props 设定合理的 fallback
- 使用
...rest透传:当包装组件时,用const { class: className, ...rest } = Astro.props收集并透传属性
astro--- // 包装组件的最佳实践 interface Props { class?: string; variant?: 'primary' | 'secondary'; } const { class: className = '', variant = 'primary', ...rest } = Astro.props satisfies Props; --- <div class={`btn btn-${variant} ${className}`} {...rest}> <slot /> </div>
Slots:组件的内容分发
如果说 Props 传递的是"数据",那么 Slots 传递的就是"内容"。Slots 让组件成为可复用的布局容器。
默认插槽
astro--- // Layout.astro const { title } = Astro.props; --- <html> <head><title>{title}</title></head> <body> <main> <slot /> <!-- 所有子内容将渲染在这里 --> </main> </body> </html>
使用时直接在组件标签内放入内容:
astro--- import Layout from './Layout.astro'; --- <Layout title="我的页面"> <h1>页面标题</h1> <p>这些内容会出现在 <slot /> 的位置</p> </Layout>
命名插槽
当组件需要多个内容入口时,使用命名插槽:
astro--- // PageLayout.astro const { title } = Astro.props; --- <div class="page"> <header> <slot name="header" /> <!-- 命名插槽 --> </header> <main> <slot /> <!-- 默认插槽 --> </main> <footer> <slot name="footer" /> <!-- 命名插槽 --> </footer> </div>
使用命名插槽:
astro--- import PageLayout from './PageLayout.astro'; --- <PageLayout title="首页"> <nav slot="header"> <a href="/">首页</a> <a href="/about">关于</a> </nav> <!-- 没有 slot 属性的内容进入默认插槽 --> <h1>欢迎</h1> <p>这是主要内容</p> <p slot="footer">版权信息</p> </PageLayout>
插槽的 Fallback 内容
插槽可以设置默认内容,当没有传入对应内容时自动显示:
astro--- // Card.astro const { title } = Astro.props; --- <div class="card"> <h2>{title}</h2> <div class="body"> <slot> <p>暂无内容</p> <!-- Fallback:未传入内容时显示 --> </slot> </div> </div>
插槽传递(Slot Forwarding)
在嵌套布局中,子布局可以将插槽"透传"给父布局:
astro--- // BaseLayout.astro --- <html> <body> <slot name="head" /> <slot /> </body> </html>
astro--- // HomeLayout.astro import BaseLayout from './BaseLayout.astro'; --- <BaseLayout> <slot name="head" slot="head" /> <slot /> </BaseLayout>
这样最终页面使用 <HomeLayout> 时,内容会正确传递到 <BaseLayout> 的对应插槽位置。
框架组件中的 Slots
Astro 支持在 React、Vue、Svelte 等框架组件中使用插槽,但各框架的接收方式不同:
| 框架 | 默认插槽 | 命名插槽 |
|---|---|---|
| React / Preact / Solid | children prop | slotName 顶级 prop |
| Vue | <slot /> | <slot name="xxx" /> |
| Svelte | <slot /> | <slot name="xxx" /> |
注意:传给框架组件的命名插槽名会从 kebab-case 转为 camelCase(如 slot="my-header" 在 React 中变为 myHeader prop)。
常见陷阱与注意事项
- 前置脚本不等于客户端脚本:
---中的代码在服务端执行,需要交互逻辑时应使用<script>标签或client:*指令 - 模板表达式是静态的:
{variable}在构建时求值,不是响应式绑定 - Props 无法传递函数:Astro 组件的 Props 是序列化传递的,函数和类实例无法通过 Props 传递
- 样式隔离是默认行为:不要假设子组件能继承父组件的 class 样式
- 组件默认是静态的:需要客户端交互时,必须使用
client:load、client:visible等水合指令
astro--- // 静态组件 vs 交互组件 import StaticCard from './StaticCard.astro'; // 始终静态 import InteractiveCounter from './Counter.jsx'; // 需要水合指令 --- <StaticCard title="静态内容" /> <!-- client:load = 页面加载时立即水合 --> <InteractiveCounter client:load /> <!-- client:visible = 进入视口时才水合,节省资源 --> <InteractiveCounter client:visible />
总结
Astro 组件的设计哲学是默认静态、按需交互:
- 三大结构:前置脚本处理服务端逻辑,模板渲染 HTML,样式自动隔离
- Props:通过
Astro.props传递数据,配合 TypeScript 类型约束确保安全 - Slots:通过默认插槽和命名插槽实现内容分发,支持嵌套透传和跨框架使用
- 核心原则:能静态就不动态,需要交互时使用
client:*水合指令
掌握这三个核心概念,就能构建出结构清晰、性能优秀的 Astro 应用。