Astro 入门:组件化开发你的第一个页面
上篇解释了为什么白物集选择 Astro 作为前端框架。今天我们直接动手——用 Astro 组件拼出一个页面,看看 .astro 文件到底怎么工作。
一个 .astro 文件就是"三个区"
Astro 组件文件只有一个 .astro 后缀,内部分三段:组件脚本区(--- 包裹)、HTML 模板区、样式/脚本区。看白物集导航栏的真实代码:
---
// ★ 组件脚本区 — 跑在服务端,浏览器看不见
interface Props {
title: string;
items?: { label: string; href: string }[];
}
const { title, items = [] } = Astro.props;
---
<!-- ★ HTML 模板区 — 服务端渲染,最终输出 HTML -->
<nav class="navbar">
<a href="/">{title}</a>
<ul>
{items.map((item) => (
<li><a href={item.href}>{item.label}</a></li>
))}
</ul>
</nav>
<style>
/* ★ 组件样式 — 自动 scoped,只对本组件生效 */
.navbar { ... }
</style>
<script>
// ★ 客户端脚本 — 浏览器下载并执行
document.querySelector('.navbar')?.addEventListener(...);
</script>
--- 里面的代码只跑一次(服务端渲染时),不会发到浏览器。这意味着你可以在这里写 fetch、读数据库、导入 npm 包——用户收到的只是最终的 HTML。
Props:组件怎么接收数据
组件通过 Astro.props 拿到外部传入的数据。白物集的文章卡片是个典型例子:
---
// ArticleCard.astro
export interface Props {
id: number;
title: string;
category: string;
abstract: string;
date: string;
tags?: string[];
}
const { id, title, category, abstract, date, tags = [] } = Astro.props;
---
<a href={`/articles/${id}`} class="card">
<div class="tag">{category}</div>
<h3>{title}</h3>
<p>{abstract}</p>
<span>📅 {date}</span>
{tags.length > 0 && <span>🏷️ {tags.slice(0, 3).join(' · ')}</span>}
</a>
定义 Props 接口不是为了好看——Astro 会根据接口对传入参数做类型检查。卡片在首页这样使用:
---
import ArticleCard from '../components/ArticleCard.astro';
---
<ArticleCard id={42} title="标题" category="科技前沿" abstract="摘要" date="2026-07-11" />
模板里的 { } 是 Astro 表达式语法,支持 JS 表达式、三元运算符、.map() 渲染列表、.filter() 筛选数据。不能写 if 语句,但三元运算加 && 短路几乎够用了。
组件嵌套与 Slot
Astro 组件可以组合使用。白物集的 BaseLayout.astro 是所有页面的根组件,它内部用 <slot /> 定义内容插入点:
---
// BaseLayout.astro — 引入了 NavBar,预留了 slot
import NavBar from '../components/NavBar.astro';
---
<!doctype html>
<html>
<head>
<title>{title} · 白物集</title>
<!-- SEO meta、OG、canonical… -->
</head>
<body>
<NavBar title={siteConfig.shortName} items={navItems} />
<main><slot /></main> <!-- ← 页面内容插在这里 -->
<footer>…</footer>
</body>
</html>
首页 index.astro 用 BaseLayout 包裹,传入的内容自动填充到 <slot /> 位置:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="首页">
<!-- 这里的内容会进入 BaseLayout 的 <slot /> -->
<section>
<h2>最新文章</h2>
<!-- 文章列表… -->
</section>
</BaseLayout>
Astro 还支持具名 slot(<slot name="jsonld" />),用于在特定位置注入内容——白物集的文章页就用这个特性在 <head> 中注入 JSON-LD 结构化数据:
<BaseLayout title={article.title} type="article">
<Fragment slot="jsonld">
<script type="application/ld+json">
{"@context": "https://schema.org", "@type": "Article", …}
</script>
</Fragment>
</BaseLayout>
样式作用域陷阱
Astro 的 <style> 默认会被自动 scoped——编译时给当前组件的 HTML 元素添加一个哈希属性,CSS 选择器自动补上这个属性选择器。这就是 NavBar.astro 里的 .navbar { } 不会泄漏到页面其他部分的原因。
但是如果你用了 <style is:global>,样式就会全局生效。白物集的 NavBar 利用这个特性实现 logo 配色随主题切换:
<style is:global>
/* 全局作用 — 因为 .logo-bg 在 SVG 子元素上,scoped 选择器够不到 */
.logo-bg { fill: url(#navGrad); }
[data-theme="dark"] .logo-bg { fill: rgba(255,255,255,0.08); }
</style>
避坑经验:SVG 内联路径和子组件嵌套是 Scoped 样式失效的高发区。遇到样式不生效时,先在浏览器检查元素确认哈希属性是否存在,再决定要不要加 is:global。
实战:拼一个页面
现在你应该能看懂白物集首页 index.astro 的完整结构了——它引入 BaseLayout,在服务端用 getArticles() 拿到数据,按 AI/Apple/科技频道分组,用 .filter().sort().map() 渲染卡片列表。全部在 --- 块里完成,浏览器收到的只有干净的 HTML。
下一篇我们进入白物集的真实样式系统——用 daisyUI 搭出统一、可维护的 UI 层,看 CSS 框架怎么和 Astro 组件体系配合。