Astro-构建组件

Astro 组件非常灵活。一个 Astro 组件可以小到只是一段 HTML 代码片段,例如一组常用的 <met…

组件

Astro 组件是 Astro 项目的基础构建模块。它们是纯 HTML、无需客户端运行时的模板组件,并使用了 .astro 的文件名后缀。

Astro 组件非常灵活。一个 Astro 组件可以小到只是一段 HTML 代码片段,例如一组常用的 <meta> 标签,用于简化 SEO 设置;也可以是可复用的 UI 元素,比如页眉(header)或用户资料卡(profile card);甚至还可以包含整个页面布局(layout),或者,当组件位于特殊的 src/pages/ 文件夹中时,它本身就可以是一整个页面。

Astro 组件最重要的一点是:它们不会在客户端(浏览器)中渲染。Astro 组件会在构建时(build-time)或按需(on-demand)渲染为纯 HTML。你可以在组件的前端脚本区域(frontmatter)中编写 JavaScript 代码,但这些代码都会在最终发送给用户浏览器的页面中被完全剔除。 最终结果是一个加载更快的网站,并且默认情况下不会添加任何 JavaScript 负担。

当你的 Astro 组件确实需要在客户端进行交互时,你可以使用标准的 HTML <script> 标签,或者引入 UI 框架组件(如 React、Vue、Svelte 等),将它们作为所谓的 “客户端岛屿(client islands)”

对于需要渲染个性化或动态内容的组件,你可以通过添加一个 server 指令 来推迟其在服务器端的渲染。这些 “服务端岛屿(server islands)” 会在其内容准备好后再进行渲染,从而不会阻塞整个页面的加载

组件结构

Astro 组件是由两个主要部分所组成的——组件 script组件模板。每个部分分工处理最终呈现出一个既容易使用,又有足够的表现力来实现你的想象的框架。

src/components/EmptyComponent.astro

---
// 组件脚本(JavaScript)
---
<!-- 组件模板(HTML + JS 表达式)-->

组件脚本

Astro 使用代码围栏(---)来识别 Astro 组件中的组件脚本。如果你以前写过 Markdown,你可能已经熟悉了叫做 frontmatter 的类似概念。Astro 的组件脚本的想法直接受到了这个概念的启发。

你可以使用组件脚本来编写渲染模板所需 JavaScript 代码。这可以包括:

  • 导入其他 Astro 组件
  • 导入其他框架组件,如 React
  • 导入数据,如 JSON 文件
  • 从 API 或数据库中获取内容
  • 创建你要在模板中引用的变量

src/components/MyComponent.astro

js
---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';

// 访问传入的组件参数,如 `<X title="Hello, World"/>`
const { title } = Astro.props;

// 获取外部数据,甚至可以从私有 API 和数据库中获取
const data = await fetch ('SOME_SECRET_API_URL/users').then (r => r.json ());
---
<!-- 你的模板在这! -->

代码围栏的设计是为了保证你在其中编写的 JavaScript 被“围起来”。它不会逃到你的前端应用程序中,或落入你的用户手中。你可以安全地在这里写一些昂贵或敏感的代码(比如调用你的私人数据库),而不用担心它会出现在你的用户的浏览器中

组件模板

在组件脚本下面的是组件模板。组件模板决定了你的组件的 HTML 输出。

如果你在这里写普通的 HTML,你的组件将在任何 Astro 页面上呈现它被导入和使用的 HTML。

然而,Astro 的组件模板语法也支持 JavaScript 表达式Astro 的 <style><script> 标签导入的组件,以及一些 特殊的 Astro 指令(directives)。在组件脚本(即 frontmatter 或 <script> 区域)中定义的数据和变量,可以在组件模板中使用,用于生成动态创建的 HTML 内容

src/components/MyFavoritePokemon.astro

js
---
// 你的组件脚本在这!
import Banner from '../components/Banner.astro';
import Avatar from '../components/Avatar.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- 支持 HTML 注释! -->
{/* JS注释语法也是有效的! */}

<Banner />
<h1>你好,世界!</h1>

<!-- 使用组件脚本中的 props 和其他变量: -->
<p>{title}</p>

<!-- 延迟组件渲染并提供回退加载内容: -->
<Avatar server:defer>
  <svg slot="fallback" class="generic-avatar" transition:name="avatar">...</svg>
</Avatar>

<!-- 包含了使用 `client:` 指令以进行水合的其他 UI 框架组件: -->
<ReactPokemonComponent client:visible />

<!-- 混合 HTML 和 JavaScript 表达式,类似于 JSX-->
<ul>
  {myFavoritePokemon.map ((data) => <li>{data.name}</li>)}
</ul>

<!-- 使用模板指令从多个字符串甚至对象来构建类名! -->
<p class:list={["add", "dynamic", { classNames: true }]} />

组件参数

Astro 组件可以定义和接受参数。然后,这些参数可用于组件模板以呈现 HTML。可以在 frontmatter script 中的 Astro.props 中使用。

这是一个接收 greetingname 参数的组件示例。请注意,要接收的参数是从全局 Astro.props 对象中解构的。

GreetingHeadline.astro

js
---
// 使用:<GreetingHeadline greeting="你好" name="朋友" />
const { greeting, name } = Astro.props
---
<h2>{greeting},{name}!</h2>

当该组件在其他 Astro 组件、布局或页面中导入并渲染时,可以将这些 props 作为属性传递:

src/components/GreetingCard.astro

js
---
import GreetingHeadline from './GreetingHeadline.astro';
const name = "Astro";
---
<h1>Greeting Card</h1>
<GreetingHeadline greeting="嗨" name={name} />
<p>希望你有美好的一天!</p>

你也可以使用 TypeScript 中的 interface 来定义 Props 类型。Astro 会自动在你的 frontmatter 中找到 Props interface,并为你的项目提供类型警告/错误。这些 props 也可以在从 Astro.props 解构时给出默认值。

src/components/GreetingHeadline.astro

js
---
interface Props {
  name: string;
  greeting?: string;
}

const { greeting = "你好", name } = Astro.props;
---
<h2>{greeting},{name}!</h2>

当没有提供组件参数时,可以给它默认值来使用。

src/components/GreetingHeadline.astro

js
---
const { greeting = "你好", name = "宇航员" } = Astro.props;
---
<h2>{greeting},{name}!</h2>

插槽

<slot /> 元素是嵌入外部 HTML 内容的占位符,你可以将其他文件中的子元素注入(或“嵌入”)到组件模板中。

默认情况下,传递给组件的所有子元素都将呈现在 <slot /> 中。

props(属性) 不同,props 是传递给 Astro 组件的参数,可以通过 Astro.props 在组件的任意位置使用; 而 slots(插槽) 则是在组件模板中渲染子级的 HTML 元素,它们会在被书写的位置直接输出内容。

src/components/Wrapper.astro

js
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <Logo />
  <h1>{title}</h1>
  <slot />  <!-- children will go here -->
  <Footer />
</div>

src/pages/fred.astro

js
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
  <h2>All about Fred</h2>
  <p>Here is some stuff about Fred.</p>
</Wrapper>

这种模式是 Astro 布局组件 的基础:整个 HTML 内容的页面可以用 <SomeLayoutComponent></SomeLayoutComponent> 标签围起来并发送到组件,以在那里定义的公共页面元素中呈现。

命名插槽

Astro 组件也可以有命名插槽。这允许你仅将具有相应插槽名称的 HTML 元素传递到插槽的位置。

src/components/Wrapper.astro

js
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <!--  带有 `slot="after-header"` 属性的子项在这 -->
  <slot name="after-header" />
  <Logo />
  <h1>{title}</h1>
  <!--  没有 `slot` 或有 `slot="default"` 属性的子项在这 -->
  <slot />
  <Footer />
  <!--  带有 `slot="after-footer"` 属性的子项在这 -->
  <slot name="after-footer" />
</div>

要将 HTML 内容注入特定插槽,可以在任何子元素上使用 slot 属性来指定插槽的名称。组件的所有其他子元素将被注入到 default(未命名)的 <slot /> 中。

src/pages/fred.astro

js
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred 的页面">
  <img src="https://my.photo/fred.jpg" slot="after-header" />
  <h2>关于 Fred 的一切</h2>
  <p>这里有一些关于 Fred 的东西。</p>
  <p slot="after-footer">版权所有 2022</p>
</Wrapper>

要将多个 HTML 元素传递到组件的 <slot/> 占位符中而无需包装 <div>,可以在 Astro 的 <Fragment/>组件上使用 slot="" 属性:

src/components/CustomTable.astro

js
---
// 创建一个自定义的表格,为表头和表格主体内容设置具名的 slot 占位符
---
<table class="bg-white">
  <thead class="sticky top-0 bg-white"><slot name="header" /></thead>
  <tbody class="[&_tr:nth-child(odd)]:bg-gray-100"><slot name="body" /></tbody>
</table>

使用 slot="" 属性以指定 "header""body" 内容以注入多行和多列的 HTML 内容。也可以对单个 HTML 元素进行样式设置:

src/components/StockTable.astro

html
---
import CustomTable from './CustomTable.astro';
---
<CustomTable>
  <Fragment slot="header"> <!-- 传递表头 -->
    <tr><th>产品名称</th><th>库存单位</th></tr>
  </Fragment>
  <Fragment slot="body"> <!-- 传递表格主体 -->
    <tr><td>人字拖</td><td>64</td></tr>
    <tr><td>靴子</td><td>32</td></tr>
    <tr><td>运动鞋</td><td class="text-red-500">0</td></tr>
  </Fragment>
</CustomTable>

请注意,命名插槽必须是组件的直接子级。不能通过嵌套元素传递命名插槽。

插槽回退内容

插槽还可以渲染回退内容。当没有匹配的子元素传递给插槽时,<slot /> 元素将呈现其自己的占位符子元素。

src/components/Wrapper.astro

js
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <Logo />
  <h1>{title}</h1>
  <slot>
    <p>当没有子项传入插槽时使用此回退</p>
  </slot>
  <Footer />
</div>

传递插槽

插槽可以传递给其他组件。例如,在创建嵌套布局时:

src/layouts/BaseLayout.astro

html
---
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <!--命名插槽-->
    <slot name="head" />
  </head>
  <body>
    <slot />
  </body>
</html>

src/layouts/HomeLayout.astro

html
---
import BaseLayout from "./BaseLayout.astro";
---
<BaseLayout>
  <!--命名与传递插槽-->
  <slot name="head" slot="head" />
  <slot />
</BaseLayout>

通过在 <slot /> 标记上使用 nameslot 属性,可以将具名插槽传递给另一个组件。

HTML 组件

Astro 支持导入和使用 .html 文件作为组件,或者将这些文件放在 src/pages 子目录下作为页面。如果你正在复用一个没有使用框架的现有网站代码,或者你想确保你的组件没有动态功能,你可能会需要使用 HTML 组件。

HTML 组件必须只包含有效的 HTML,因此缺乏关键的 Astro 组件功能:

  • 它们不支持 frontmatter、服务器端导入或动态表达式。
  • 任何 <script> 标签都不会被打包,默认视为带有 is:inline 指令进行处理。
  • 它们只能 引用 public/ 文件夹中的资源。

注意

HTML 组件内的 <slot /> 元素会像在 Astro 组件中那样工作。要使用 HTML Web Component Slot 元素,请在 <slot> 元素中添加 is:inline。

布局

布局是特殊的 Astro 组件,可用于创建可重用的页面模板。

我们通常将“布局”用于提供共享页面的常用 UI 元素(如标题、导航栏和页脚)的 Astro 组件。典型的 Astro 布局组件为 Astro、Markdown 或 MDX 页面提供:

  • 一个 页面外壳<html><head><body> 标签)
  • 一个 来指定单个页面内容应该被注入的位置。

但是,布局组件没有什么特别的!它们可以像任何其他 Astro 组件一样接受 props导入和使用其他组件。它们可以包含UI 框架组件客户端脚本。它们甚至不必提供完整的页面外壳,而是可以用作部分 UI 模板。

然而,如果布局组件确实包含页面外壳,那么它的 <html> 元素必须是该组件中所有其他元素的父元素。

布局组件通常放置在项目中的 src/layouts 目录中,但这不是必须的。你可以选择将它们放置在项目中的任何位置。你甚至可以通过在布局组件名称前面加上“_”将布局组件与页面放在同一个文件夹中。

示例布局

src/layouts/MySiteLayout.astro

html
---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
const { title } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <BaseHead title={title}/>
  </head>
  <body>
    <nav>
      <a href="#">主页</a>
      <a href="#">文章</a>
      <a href="#">联系</a>
    </nav>
    <h1>{title}</h1>
    <article>
      <slot /> <!-- 你的内容会被插入到这里 -->
    </article>
    <Footer />
  </body>
  <style>
    h1 {
      font-size: 2rem;
    }
  </style>
</html>

src/pages/index.astro

js
---
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout title="Home Page">
  <p>我的页面内容,被包裹在一个布局中!</p>
</MySiteLayout>

在布局中使用 TypeScript

任何 Astro 布局都可以通过提供 props 类型来引入类型安全和自动补全功能:

js
---
interface Props {
  title: string;
  description: string;
  publishDate: string;
  viewCount: number;
}
const { title, description, publishDate, viewCount } = Astro.props;
---

Markdown 布局

页面布局对于没有任何页面格式的 Markdown 页面尤为有用。

Astro 提供了一个特殊的 layout frontmatter 属性,用于在 src/pages/ 目录中使用基于文件的路由的单个 .md 文件来指定使用哪个 .astro 组件作为页面布局。这个组件允许你提供 <head> 内容,例如 meta 标签(例如 <meta charset="utf-8">)和样式,用于 Markdown 页面。默认情况下,指定的组件可以自动访问 Markdown 文件中的数据。

layout是一个特殊属性,当使用内容集合来查询和渲染你的内容时,不会被识别。

src/pages/page.md

yaml
---
layout: ../layouts/BlogPostLayout.astro
title: "Hello, World!"
author: "Matthew Phillips"
date: "09 Aug 2022"
---
所有的 frontmatter 属性都可以作为 Astro 布局组件的 props。

`layout` 属性是 Astro 提供的唯一一个特殊属性。

你可以在 `src/pages/` 目录下的 Markdown 文件中使用它。

一个典型的 Markdown 页面布局包括:

  1. 一个 frontmatter prop,用于访问 Markdown 页面的 frontmatter 和其他数据。
  2. 一个默认的 <slot/>,用于指定页面的 Markdown 内容应该被渲染的位置。

src/layouts/BlogPostLayout.astro

js
---
// 1. `frontmatter` prop 提供了访问 frontmatter 和其他数据的能力
const { frontmatter } = Astro.props;
---
<html>
  <head>
    <!-- 添加其他 Head 元素,例如样式和 meta 标签。 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <!-- 添加其他 UI 组件,例如通用的头部和页脚。 -->
    <h1>{frontmatter.title} by {frontmatter.author}</h1>
    <!-- 2. 渲染的 HTML 将被传入默认插槽。 -->
    <slot />
    <p>写于: {frontmatter.date}</p>
  </body>
</html>

你可以使用 MarkdownLayoutProps 帮助程序设置布局的 Props 类型

src/layouts/BlogPostLayout.astro

js
---
import type { MarkdownLayoutProps } from 'astro';

type Props = MarkdownLayoutProps<{
  // 在这里定义 frontmatter 属性
  title: string;
  author: string;
  date: string;
}>;

// 现在,`frontmatter`、`url` 和其他 Markdown 布局属性
// 可以通过 TypeScript 类型安全地访问
const { frontmatter, url } = Astro.props;
---

Markdown 布局的 Props

一个 Markdown 布局将通过 Astro.props 访问以下信息:

  • file - 此文件的绝对路径(例如 /home/user/projects/.../file.md)。
  • url - 此页面的 URL(例如 /zh-cn/guides/markdown-content)。
  • frontmatter - Markdown 或 MDX 文档中所有的 frontmatter。
    • frontmatter.file - 与顶级 file 属性相同。
    • frontmatter.url - 与顶级 url 属性相同。
  • headings - Markdown 或 MDX 文档中的标题(h1 -> h6)列表及其相关元数据。此列表遵循类型:{ depth: number; slug: string; text: string }[]
  • rawContent() - 返回原始 Markdown 文档的字符串的函数。
  • compiledContent() - 返回 Markdown 文档编译为 HTML 字符串的异步函数。

注意

Markdown 布局将通过 Astro.props 访问其文件的所有 可用属性但有两点关键区别:

  • 标题信息(即 h1 -> h6 元素)可通过 headings 数组访问,而不是 getHeadings() 函数。
  • fileurl 也作为嵌套的 frontmatter 属性(即 frontmatter.urlfrontmatter.file)可用。

嵌套布局

布局组件无需包含整个页面的 HTML。你可以将布局分解为更小的组件,然后重用这些组件以创建更灵活、更强大的布局。

例如,BlogPostLayout.astro 布局组件可以对文章的标题、日期和作者进行样式化。然后,BaseLayout.astro 可以处理页面模板的其余部分,例如导航和页脚。你还可以将从文章接收的 props 传递给另一个布局,就像任何其他嵌套组件一样。

src/layouts/BlogPostLayout.astro

js
---
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout url={frontmatter.url}>
  <h1>{frontmatter.title}</h1>
  <h2>文章作者: {frontmatter.author}</h2>
  <slot />
</BaseLayout>

样式和 CSS

Astro 的设计为了使设计和编写 CSS 变得轻而易举。直接在 Astro 组件中编写你自己的 CSS,或者导入你最喜欢的 CSS 库,如 Tailwind 外。也支持高级样式设计语言,如 SassLess

在 Astro 进行设计

为 Astro 组件设计样式,就像在你的组件或页面模板上添加 <style> 标签一样容易。当你在 Astro 组件内置了 <style> 标签时,Astro 就会自动检测 CSS 并开始为你处理样式。

src/components/MyComponent.astro

html
<style>
  h1 { color: red; }
</style>

作用域样式

Astro <style> 标签内的 CSS 规则默认自动限定范围。作用域样式在幕后编译,只适用于写在同一组件内的 HTML。你在 Astro 组件中编写的 CSS 会自动封装在该组件中。

现有 CSS:

src/pages/index.astro

html
<style>
  h1 {
    color: red;
  }

  .text {
    color: blue;
  }
</style>

编译为:

js
<style>
  h1[data-astro-cid-hhnqfkh6] {
     color: red;
  }

  .text[data-astro-cid-hhnqfkh6] {
    color: blue;
  }
</style>

作用域样式不会泄漏,也不会影响你网站的其他部分。在 Astro 中,可以使用像 h1 {}p {} 这样的低特定性选择器,因为在最终输出中它们与作用域一起被编译。

作用域样式也不适用于模板内的其他 Astro 组件。如果你需要修改子组件的样式,可以考虑将该组件包裹在 <div>(或其他元素)中,然后你就可以对其编写样式了。

作用域样式的特定性被保留,允许它们与其他 CSS 文件或 CSS 库一起工作,同时仍然保留防止样式应用于组件之外的独特边界。

全局样式

虽然我们推荐大多数组件使用范围化的样式,但有时你可能有必要使用全局的、非限定范围的 CSS。你可以通过 <style is:global> 属性选择不自动限定 CSS 范围。

src/components/GlobalStyles.astro

html
<style is:global>
  /* 无作用域,直接传递给浏览器。
     适用于网站上的所有 <h1> 标签。 */
  h1 { color: red; }
</style>

你也可以使用 :global() 选择器在同一个 <style> 标签中混合全局和作用域 CSS 规则。这可以将 CSS 样式应用于子组件。

html
<style>
  /* 仅限于此组件。 */
  h1 { color: red; }
  /* 混合:仅限于子组件的 <h1> 标签。 */
  article :global(h1) {
    color: blue;
  }
</style>
<h1>Title</h1>
<article><slot /></article>

class:list 组合类名

如果你需要在元素上动态组合类名,你可以在 .astro 文件中使用 class:list 工具属性。

src/components/ClassList.astro

html
---
const { isRed } = Astro.props;
---
<!-- 如果 `isRed` 是真值,class 将是 "box red"。 -->
<!-- 如果 `isRed` 是假值,class 将是 "box"。 -->
<div class:list={['box', { red: isRed }]}><slot /></div>

<style>
  .box { border: 1px solid blue; }
  .red { border-color: red; }
</style>

CSS 变量

添加于: astro@0.21.0

Astro <style> 可以引用页面上任何可用的 CSS 变量。你也可以在组件的 frontmatter 中直接使用 define:vars 指令来传递 CSS 变量。

src/components/DefineVars.astro

js
---
const foregroundColor = "rgb(221 243 228)";
const backgroundColor = "rgb(24 121 78)";
---
<style define:vars={{ foregroundColor, backgroundColor }}>
  h1 {
    background-color: var(--backgroundColor);
    color: var(--foregroundColor);
  }
</style>
<h1>Hello</h1>

参见 指令参考 页面,了解更多关于 define:vars 的信息。

传递 class 给子组件

在 Astro 中,像 class 这样的 HTML 属性 并不会自动传递给子组件。

相反,你需要在子组件中显式地声明一个 class 属性(prop),然后将它应用到组件的根元素上。 如果你在前端脚本中对 props 进行了解构赋值(destructuring),必须重命名 class,因为 class 是 JavaScript 的保留字

在使用 Astro 的默认 作用域样式策略(scoped style strategy) 时,还需要传递一个 data-astro-cid-* 属性。

为什么要传 data-astro-cid-*

Astro 的 作用域样式(Scoped CSS) 机制,会自动为每个组件生成一个类似:<div data-astro-cid-abcd1234>

当你把 class 和其他属性传给子组件时,如果没有传递 data-astro-cid-*,Astro 可能无法知道子组件属于哪个样式作用域。这会导致父组件的样式无法正确应用到子组件内部。

你可以通过将 ...rest(其余属性)传递给组件来实现这一点。

如果你已将 scopedStyleStrategy 设置为 'class''where',则不需要传递 ...rest 属性。

关于 scopedStyleStrategy

Astro 默认的样式隔离策略是通过 data-astro-cid-* 实现的。但如果你在配置中将 scopedStyleStrategy 改成 'class''where', Astro 会改用其他方式(例如类名或 CSS where() 选择器)来实现样式作用域,这样就不再需要手动传 ...rest

src/components/MyComponent.astro

js
---
const { class: className, ...rest } = Astro.props;
---
<div class={className} {...rest}>
  <slot/>
</div>

备注

解释:

  • const { class: className } 是因为 class 是 JavaScript 保留字,不能直接用。
  • ...rest 表示接收其他传入的属性(比如 iddata-* 等),然后继续传下去。
  • <div class={className} {...rest}> 把这些属性真正应用到组件的根元素上。

src/pages/index.astro

html
---
import MyComponent from "../components/MyComponent.astro"
---
<style>
  .red {
    color: red;
  }
</style>
<MyComponent class="red">这将是红色!</MyComponent>

父组件的作用域样式(Scoped styles from parent components)

由于 data-astro-cid-* 属性会让子组件包含在父组件的样式作用域中, 因此,父组件的样式可能会级联(cascade)影响到子组件

为了避免出现这种意料之外的样式干扰,请确保在子组件中使用唯一的类名(class name)

内联样式

你可以使用 style 属性将 HTML 元素设置为内联样式。这可以是 CSS 字符串或 CSS 属性的对象:

src/pages/index.astro

html
// 这两种写法是等价的:
<p style={{ color: "brown", textDecoration: "underline" }}>My text</p>
<p style="color: brown; text-decoration: underline;">My text</p>

外部样式

有两种使用外部全局样式表的方法:项目源文件中使用 ESM 导入;使用绝对链接引用 public/ 目录中的文件或托管于别处的文件。

导入本地样式表

你可以在 Astro 组件中使用 ESM 导入语法显式导入样式表。CSS 导入方式与 Astro 组件中的其他 ESM 导入一样,它应该基于组件进行引用,并且与其他导入一样必须位于组件脚本顶层

src/pages/index.astro

js
---
// Astro 会自动为你捆绑和优化这些CSS。
// 这也适用于预处理器文件,如 .scss、.styl 等。
import '../styles/utils.css';
---
<html><!-- 你的页面在这 --></html>

任何 JavaScript 文件都支持通过 ESM import导入 CSS,包括像 React 和 Preact 这样的 JSX 组件。这有助于为 React 组件编写细化的,具有针对性的样式

从 npm 包中导入样式表

如果你使用的包在导入样式文件时没有指定文件扩展名(例如 package-name/styles),你需要先更新 Astro 的配置文件

假设你从某个名为 package-name 的 npm 包中导入一个叫 normalize 的 CSS 文件,而你写的是(注意这里没有 .css 扩展名):

js
import "package-name/normalize";

为了确保 Astro 能够正确地预渲染(prerender)你的页面,你需要在 astro.config.mjs 中将该包名称(例如 package-name)添加到配置项 vite.ssr.noExternal 的数组中:

js
export default {
  vite: {
    ssr: {
      noExternal: ['package-name']
    }
  }
}

你也可能需要从外部 npm 包中加载样式表。它常用于像导入 Open Props 这样的工具类。如果你的包建议使用文件扩展名(即 package-name/styles.css 而不是 package-name/styles),那么它的行为应该与本地样式表一致:

注意

这是针对 Vite 的设置Astro SSR 无关。

Astro 在构建时,会使用 Vite SSR(服务端渲染) 机制来提前生成 HTML。当它发现你导入一个包时,会默认将该包视为「外部依赖」(external dependency),即:不会去解析它的源代码,而是直接从打包结果中引入。 当你导入一个没有文件扩展名的样式文件时,例如:

js
import "package-name/styles";

Vite 在服务端渲染(SSR)阶段可能无法正确解析这个路径,因为它不知道这个文件到底是 .css.scss 还是 .js

于是 Astro 就需要你显式告诉它

“这个包(package-name)不要作为 external 处理,让 Vite 进去解析它的源代码。”

这正是 vite.ssr.noExternal 的作用。

你也可以使用 <link> 元素在页面上加载样式表。它是应该位于 /public 目录下的 CSS 文件的绝对路径,或者是外部网站的链接。不支持使用相对路径的 <link> href 值。

html
<head>
  <!-- 本地:/public/styles/global.css -->
  <link rel="stylesheet" href="/styles/global.css" />
  <!-- 外部 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.24.1/themes/prism-tomorrow.css">
</head>

因为这种方法使用 public/ 目录,它跳过了 Astro 提供的 CSS 处理、捆绑和压缩。

Tailwind

Astro 支持在项目中添加流行的 CSS 库、工具和框架,如 Tailwind 和其他库!

Astro 同时支持 Tailwind 3 和 4。你可以通过 CLI 命令 添加 Vite 插件 到你的项目以添加 Tailwind 4 支持,或者手动安装旧版依赖项来添加 Tailwind 3 支持

如果你要将你现有 Astro 项目的 Tailwind 3 升级到 4,你需要在添加 Tailwind 4 相关依赖的同时,删除旧版 Tailwind 3 相关依赖。

添加 Tailwind 4 支持

在 Astro >=5.2.0 中,使用 astro add tailwind 命令来安装 Tailwind 官方的 Vite 插件。如果要在早期版本的 Astro 中添加 Tailwind 4 支持,请按照 Tailwind 文档中的指引 手动添加 @tailwindcss/vite Vite 插件。

shell
npx astro add tailwind

然后,将 tailwindcss 导入到 src/styles/global.css(或你选择的其他 CSS 文件)中,使 Tailwind 类对你的 Astro 项目可用。如果你使用 astro add tailwind 命令来安装 Vite 插件,那么这个包含导入语句的文件将被默认创建。

src/styles/global.css

css
@import "tailwindcss";

将此文件导入到你希望 Tailwind 应用的页面中。这一步骤通常是在布局组件中完成的,以便 Tailwind 样式可以在共享该布局的所有页面上使用:

src/layouts/Layout.astro

js
---
import "../styles/global.css";
---

CSS 预处理器

Astro 支持通过 Vite 使用 CSS 预处理器,如 SassStylusLess

Sass 和 SCSS

npm install sass

.astro 文件中使用 <style lang="scss"><style lang="sass">

Stylus

npm install stylus

.astro 文件中使用 <style lang="styl"><style lang="stylus">

Less

npm install less

.astro 文件中使用 <style lang="less">

LightningCSS

npm install lightningcss

astro.config.mjs 中更新你的 vite 配置:

astro.config.mjs

js
import { defineConfig } from 'astro/config'

export default defineConfig({
  vite: {
    css: {
      transformer: "lightningcss",
    },
  },
})

在框架组件中

你也可以在 JS 框架内使用上述所有的 CSS 预处理程序!请务必遵循每个框架推荐的模式。

  • React / Preact: import Styles from './styles.module.scss';
  • Vue: <style lang="scss">
  • Svelte: <style lang="scss">

高级

is:inline 是一个 样式指令(style directive),用于控制样式在构建时的输出方式。它告诉 Astro: 👉 “不要把这个样式提取成单独的 CSS 文件,而是直接内联(inline)到生成的 HTML 中。”

基本示例

html
<style is:inline>
  h1 {
    color: red;
  }
</style>

<h1>Hello Astro</h1>

输出结果

html
<h1 style="color:red">Hello Astro</h1>

✅ 它能用,但 ⚠️ 有严格限制条件。 如果你使用环境或结构不符合要求,就会触发 Internal server error: No Astro CSS at index 0

触发 No Astro CSS at index 0 的原因

这个错误不是说你“语法错了”,而是 Astro 没能正确关联样式作用域上下文

主要会在以下几种情况下发生:

触发原因说明
<style is:inline> 位于最顶层页面(不是组件内)顶层 .astro 文件无法正确索引 CSS 模块,Astro 内部构建器找不到对应的 CSS 插槽。
❌ 页面包含完整的 <html><head>这会让 Astro 的样式插入逻辑失效,因为它默认会将样式注入自动生成的 <head>
❌ 页面或布局在热更新 (HMR) 时出错Astro 的 Vite 插件有 bug,会在 HMR 阶段无法找到 index 0 的样式引用。
❌ 同时使用了 is:inline + is:global两个指令不能共存。

你说的 is:inline 是正确的特性,但不能用于顶层 .astro 页面结构里。 只要它放在组件或普通页面(没有 <html> 包裹)中,就不会再报 No Astro CSS at index 0

scopedStyleStrategy

Astro 默认会把组件样式限制在当前组件内部,避免「样式污染」。为此,它必须在HTML 元素CSS 选择器之间建立“唯一对应关系”。

scopedStyleStrategy 就是定义 Astro 如何实现这种对应关系的配置。 你可以在 astro.config.mjs 中设置:

js
export default {
scopedStyleStrategy: 'attribute' // 默认策略
}

它有三种取值:

js
'attribute' | 'class' | 'where'

1️⃣ 'attribute'(默认方式)

Astro 会自动给组件里的元素添加唯一属性,例如:

html
<div data-astro-cid-abcd1234>

对应的 CSS 会是:

css
div[data-astro-cid-abcd1234] {
color: red;
}

✅ 优点:完全隔离,安全稳定 ⚠️ 缺点:要传 data-astro-cid-*,否则子组件不会被算在父作用域中

2️⃣ 'class' 模式

Astro 改用 类名 来实现作用域隔离。

示例:

html
<div class="astro-ABCD1234">

生成的 CSS:

css
div.astro-ABCD1234 {
color: red;
}

这样样式是通过类名限定的。 因为类名会自动绑定在根元素上,所以不需要再传递 ...rest

✅ 优点:兼容性更好,HTML 代码更干净

3️⃣ 'where' 模式

Astro 使用 CSS 的 :where() 选择器 实现作用域。

scopedStyleStrategy: 'where' 模式下,Astro 会给组件的根节点加上一个类名,比如:

html
<div class="astro-ABCD1234">
<p>我是组件内部的内容</p>
</div>

然后生成的样式类似:

css
:where(.astro-ABCD1234) p {
    color: red;
}

这意味着组件的所有元素都自动包含在 .astro-ABCD1234 的作用域中。

此时浏览器天然知道 .astro-child 位于 .astro-parent 之内,所以作用域关系已经在 CSS 层面被解析完成

样式不生效BUG

js
---
import { getCollection } from "astro:content";

const docs = await getCollection("docs");
let count = 0;
---

<meta charset="UTF-8" />
<title>Markdown List</title>
<h1>Markdown List Page</h1>

<!-- 列表容器 -->
<ul id="list"></ul>

<!-- 分页容器 -->
<div class="pagination" id="pagination"></div>

<style is:global>
    body {
        font-family: sans-serif;
        padding: 20px;
    }
    ul {
        list-style: none;
        padding: 0;
    }
    li {
        margin-bottom: 6px;
    }
    .pagination {
        display: flex;
        gap: 6px;
        margin-top: 20px;
    }
    .pagination button {
        padding: 4px 10px;
        border: 1px solid #ccc;
        background: white;
        cursor: pointer;
        border-radius: 4px;
    }
    .pagination button.active {
        background: #007bff;
        color: white;
    }
    .pagination button:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }
</style>
<script is:inline define:vars={{ _docs: docs }}>
    const docs = _docs;
    console.log(docs);

    let currentPage = 1;
    let pageSize = 2;
    let maxButtons = 7;

    const listEl = document.getElementById("list");
    const paginationEl = document.getElementById("pagination");

    function getPageList(total, current, max) {
        const list = [];
        const half = Math.floor(max / 2);

        if (total <= max) {
            for (let i = 1; i <= total; i++) list.push(i);
        } else {
            let start = Math.max(2, current - half + 2);
            let end = Math.min(total - 1, current + half - 2);

            if (current <= half) {
                start = 2;
                end = max - 2;
            } else if (current >= total - half + 1) {
                start = total - max + 3;
                end = total - 1;
            }

            list.push(1);
            if (start > 2) list.push("...");
            for (let i = start; i <= end; i++) list.push(i);
            if (end < total - 1) list.push("...");
            list.push(total);
        }
        return list;
    }

    function renderList() {
        listEl.innerHTML = "";
        const total = docs.length;
        const start = (currentPage - 1) * pageSize;
        const end = start + pageSize;
        const items = docs.slice(start, end);

        for (const post of items) {
            const li = document.createElement("li");
            li.innerHTML = `<a href="/docs/${post.slug}">${post.data.title} - ${post.data.pubDate}</a>`;
            listEl.appendChild(li);
        }
    }

    function renderPagination() {
        paginationEl.innerHTML = "";
        const total = Math.ceil(docs.length / pageSize);
        const pages = getPageList(total, currentPage, maxButtons);

        const prev = document.createElement("button");
        prev.textContent = "上一页";
        prev.disabled = currentPage === 1;
        prev.onclick = () => {
            if (currentPage > 1) {
                currentPage--;
                render();
            }
        };
        paginationEl.appendChild(prev);

        for (const p of pages) {
            const btn = document.createElement("button");
            btn.textContent = p;
            if (p === "...") {
                btn.disabled = true;
            } else {
                if (p === currentPage) btn.classList.add("active");
                btn.onclick = () => {
                    currentPage = p;
                    render();
                };
            }
            paginationEl.appendChild(btn);
        }

        const next = document.createElement("button");
        next.textContent = "下一页";
        next.disabled = currentPage === total;
        next.onclick = () => {
            if (currentPage < total) {
                currentPage++;
                render();
            }
        };
        paginationEl.appendChild(next);
    }

    function render() {
        renderList();
        renderPagination();
    }

    render();
</script>

如果不为<style>设置is:global,针对button的样式就会失效,这是由于button是js生成的,所以没有类似data-astro-cid-xesgsyug的属性

html
<h1 data-astro-cid-xesgsyug="">Markdown List Page</h1>

image-20251016194259539

对于

css
.pagination button {
    padding: 4px 10px;
    border: 1px solid #ccc;
    background: white;
    cursor: pointer;
    border-radius: 4px;
}

最终是转化为:

css
.pagination[data-astro-cid-xesgsyug] button[data-astro-cid-xesgsyug] {
    padding: 4px 10px;
    border: 1px solid #ccc;
    background: white;
    cursor: pointer;
    border-radius: 4px;
}

设置完is:global之后

一个都不加属性了 image-20251016194505494

使用自定义字体

本指南将向你展示如何将网页字体添加到你的项目中,并且在组件中使用这些字体。

使用本地字体文件

这个例子将演示使用字体文件添加自定义字体 DistantGalaxy.woff

  1. 添加你的字体文件到 public/fonts/ 目录。
  2. 将以下@font-face 语句添加到你的CSS中。它可以位于你引入的全局 .css 文件,或在<style is:global> 块中, 也可以是你想要使用字体的特定布局或组件中的 <style> 块中。
css
/* 注册你的自定义字体并告诉浏览器它在哪里 */
@font-face {
  font-family: 'DistantGalaxy';
  src: url('/fonts/DistantGalaxy.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

使用 font-family 值为组件或布局中的元素设置字体样式。在此示例中,自定义字体将应用于 <h1> 标题,而不会用于段落 <p>

html
---
---

<h1>In a galaxy far, far away...</h1>

<p>Custom fonts make my headings much cooler!</p>

<style>
h1 {
  font-family: 'DistantGalaxy', sans-serif;
}
</style>

使用字体资源包

Fontsource(字体资源包)项目简化了使用 Google Fonts 和其他开源字体的使用。它提供了 npm 模块,你可以为要使用的字体安装。

  1. Fontsource 的目录中找到你想用的字体。此例子将选择 Twinkle Star字体。
  2. 安装你选择的字体的包。
shell
npm install @fontsource/twinkle-star

你可以在 Fontsource 网站上每个字体页面的“快速安装”部分找到正确的包名称。包名总是以 @fontsource/@fontsource-variable/ 字体名称开头。

在要使用字体的布局或组件中导入字体包。通常,需要在通用布局组件中执行此操作,以确保该字体在你的网站上可用。

导入(import)将自动添加 @font-face 以设置字体所需的必要属性。

src/layouts/BaseLayout.astro

js
---
import '@fontsource/twinkle-star';
---

使用字体名称作为 font-family 值,正如 Fontsource 网站中的例子所示。这适用于任何可以在 Astro 项目中编写 CSS 的地方。

css
h1 {
  font-family: "Twinkle Star", cursive;
}

处理脚本和事件

你可以通过在组件模版中使用 <script> 标签发送 JavaScript 到浏览器,并且为 Astro 组件添加各种功能。

脚本用来向你的网站添加交互性,比如处理事件或者动态更新内容,而无需任何像 React、Svelte 或者 Vue 这样的 UI 框架。这避免了随框架一起分发大量 JavaScript 的开销,也不需要你掌握任何额外框架就能创建功能齐全的网站或应用。

客户端脚本

脚本可以用来添加事件监听器,发送分析数据,播放动画,以及其他所有 JavaScript 可以在 Web 上做的事情。

Astro 会自动增强 HTML 标准的 <script> 标签,提供打包、TypeScript 等功能。有关更多详细信息,请参阅 Astro 如何处理脚本

src/components/ConfettiButton.astro

html
<button data-confetti-button>Celebrate!</button>

<script>
  // 从 npm 包导入
  import confetti from 'canvas-confetti';

  // 找到页面上的组件 DOM。
  const buttons = document.querySelectorAll('[data-confetti-button]');

  // 当 button 被点击时,添加事件监听器来触发 confetti。
  buttons.forEach((button) => {
    button.addEventListener('click', () => confetti());
  });
</script>

想在脚本中使用Astro变量需要使用指令define:vars

html
---
import { getCollection } from "astro:content";

const docs = await getCollection("docs");
let count = 0;
---
<script define:vars={{ _docs:docs }}>
      const docs = _docs
</script>

脚本处理

默认情况下,Astro 会用以下方式处理不包含属性(除了 src 之外)的 <script> 标签:

  • TypeScript 支持: 所有脚本默认使用 TypeScript。
  • 导入打包: 导入本地文件或 npm 模块,会被打包在一起。
  • 模块类型: 处理后的脚本会自动变成 type="module"
  • 去重: 如果包含 <script> 的组件在页面上被多次使用,该脚本只会被包含一次。
  • 自动内联: 如果脚本足够小,Astro 会将其直接内联到 HTML 中,以减少请求次数。

src/components/Example.astro

js
<script>
  // 会处理!会打包!TypeScript!
  // 也可以导入本地脚本和 npm 包。
</script>

未处理的脚本

Astro 不会处理包含除了 src 以外任何属性的 <script> 标签。

你可以添加 is:inline 指令来有意选择不对脚本进行处理。

src/components/InlineScript.astro

html
<script is:inline>
  // 将会被直接插入 HTML,不会有任何变化!
  // 不会被转换:没有 TypeScript,也没有被 Astro 解析导入。
  // 如果在组件内部使用,这段代码会为每个实例重复创建。
</script>

在你的页面中包含 JavaScript 文件

你可能希望将脚本编写为单独的 .js/.ts 文件,或者需要引用另一台服务器上的外部脚本。你可以通过在 <script> 标签的 src 属性中引用它们来做到这一点。

导入本地脚本

何时使用:当你的脚本位于 src/

src/components/LocalScripts.astro

html
<!-- `src/scripts/local.js` 脚本的相对路径 -->
<script src="../scripts/local.js"></script>

<!-- 也适用于本地 TypeScript 文件 -->
<script src="./script-with-types.ts"></script>
加载外部脚本

何时使用: 当你的 JavaScript 文件位于 public/ 或 CDN 上。

要加载在项目的 src/ 文件夹之外的脚本,请使用 is:inline 指令。当你如上所述导入脚本时,此方法会跳过 Astro 提供的 JavaScript 处理、打包和优化。

src/components/ExternalScripts.astro

html
<!-- `public/my-script.js` 脚本的绝对路径 -->
<script is:inline src="/my-script.js"></script>

<!-- 远程服务器上脚本的完整 URL -->
<script is:inline src="https://my-analytics.com/script.js"></script>

常见脚本模式

处理 onclick 和其他事件

一些 UI 框架使用自定义语法来处理事件,例如@click="..." (Vue)。Astro 更严格地遵循标准 HTML,并且不对事件使用自定义语法。

相反,你可以在 <script> 标签中使用 addEventListener 来处理用户交互。

src/components/AlertButton.astro

html
<button class="alert">Click me!</button>

<script>
  // 在页面上找到所有带有 `alert` 类的按钮。
  const buttons = document.querySelectorAll('button.alert');
  // 处理每个按钮上的点击事件。
  buttons.forEach((button) => {
    button.addEventListener('click', () => {
      alert('按钮被点击了!');
    });
  });
</script>

如果你在一个页面上有多个 <AlertButton /> 组件,Astro 将不会多次运行该脚本。脚本是打包在一起的,每页只包含一次。使用 querySelectorAll 确保此脚本将事件侦听器附加到页面上找到的具有 alert 类的每个按钮。

具有自定义元素的 Web 组件

你可以使用 Web 组件标准创建具有自定义行为的 HTML 元素。在 .astro 组件中定义 自定义元素 允许你构建交互式组件而无需 UI 框架库。

在这个例子中,我们定义了一个新的 <astro-heart> HTML 元素,用于跟踪你点击喜欢按钮的次数并使用最新计数更新 <span>

src/components/AstroHeart.astro

html
<!-- 将按钮包装在我们的自定义元素“astro-heart”中。 -->
<astro-heart>
  <button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>

<script>
  // 为我们的新 HTML 自定义元素定义行为。
  class AstroHeart extends HTMLElement {
    connectedCallback() {
      let count = 0;

      const heartButton = this.querySelector('button');
      const countSpan = this.querySelector('span');

      // 每次单击按钮时,更新计数。
      heartButton.addEventListener('click', () => {
        count++;
        countSpan.textContent = count.toString();
      });
    }
  }

  // 告诉浏览器将我们的 AstroHeart 类用于 <astro-heart> 元素。
  customElements.define('astro-heart', AstroHeart);
</script>

在这里使用自定义元素有两个优点:

  1. 除了使用 document.querySelector() 搜索整个页面,你可以使用 this.querySelector() ,它只在当前自定义元素实例中搜索。这使得一次只处理一个组件实例的子实例变得更容易。
  2. 虽然 <script> 只运行一次,但浏览器每次在页面上找到 <astro-heart> 时都会运行我们自定义元素的 connectedCallback() 方法。这意味着你可以安全地一次为一个组件编写代码,即使你打算在一个页面上多次使用该组件。

你可以在web.dev 的《可重用 Web 组件指南》MDN 对自定义元素的介绍中了解更多自定义元素。

将 frontmatter 变量传递给脚本

在 Astro 组件中,frontmatter--- 之间)中的代码在服务器上运行,而不是在浏览器中。

要将服务端变量传递给客户端脚本,可以使用 data-* 属性 在 HTML 中存储它们。脚本就可以使用 dataset 属性读取这些属性。

在此示例组件中,一个 message 属性存储在 data-message 属性中,因此自定义元素可以读取 this.dataset.message 并在浏览器中获取该属性的值。

src/components/AstroGreet.astro

html
---
const { message = '你好,世界!' } = Astro.props;
---

<!-- 将消息存储为数据属性。 -->
<astro-greet data-message={message}>
  <button>Say hi!</button>
</astro-greet>

<script>
  class AstroGreet extends HTMLElement {
    connectedCallback() {
      // 从 data(数据)属性中读取消息。
      const message = this.dataset.message;
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        alert(message);
      });
    }
  }

  customElements.define('astro-greet', AstroGreet);
</script>

现在我们可以多次使用我们的组件,并且每次都会收到不同的消息。

src/pages/example.astro

html
---
import AstroGreet from '../components/AstroGreet.astro';
---

<!-- 使用默认消息:“你好,世界!” -->
<AstroGreet />

<!-- 使用作为 props 传递的自定义消息。 -->
<AstroGreet message="构建组件的美好一天!" />
<AstroGreet message="很高兴你做到了!👋" />

当你将 props 传递给使用 React 等 UI 框架编写的组件时,这实际上是 Astro 在幕后所做的!对于带有 client:* 指令的组件,Astro 创建了一个带有 props 属性的 <astro-island> 自定义元素,该属性将你的服务器端 props 存储在 HTML 输出中。

脚本和 UI 框架相结合

<script> 标签执行时,由 UI 框架渲染的元素可能还不可用。如果你的脚本还需要处理 UI 框架组件,建议使用自定义元素。

前端框架

在构建你的 Astro 网站时,无需放弃你喜欢的前端组件框架。你可以使用自己选择的 UI 框架来创建 Astro “岛屿”(Astro Islands)。


官方前端框架集成

Astro 官方支持多种流行的前端框架,包括 React、Preact、Svelte、Vue、SolidJS 和 AlpineJS,并提供了这些框架的官方集成。

使用框架组件

在 Astro 页面、布局和组件中就像 Astro 组件一样使用你的 JavaScript 框架组件。所有组件都可放在 /src/components 目录中,或者你也可以放在任何你喜欢的地方。

要使用框架组件,你需要在 Astro 组件脚本中使用相对路径导入它们。然后在其他组件、HTML 元素和类 JSX 表达式中使用它们。

src/pages/static-components.astro

html
---
import MyVueComponent from '../components/MyReactComponent.vue';
---
<html>
  <body>
    <h1>Use Vue components directly in Astro!</h1>
    <MyVueComponent />
  </body>
</html>

默认情况下,你的框架组件将渲染为静态 HTML。这对于模板组件而言非常有用,它不需要交互和避免分发没用的 JavaScript 给用户。

激活组件

框架组件可以使用 client:* 指令来激活(hydrate)。这些是用于确定何时将组件的 JavaScript 发送到浏览器的组件属性。

使用除 client:only 以外的所有客户端指令,你的组件将首先在服务器上渲染以生成静态 HTML。根据你选择的指令,将向浏览器发送组件 JavaScript。然后组件将被激活并变成可交互式的。

src/pages/interactive-components.astro

js
---
// 示例:在浏览器中激活框架组件。
import InteractiveButton from '../components/InteractiveButton.jsx';
import InteractiveCounter from '../components/InteractiveCounter.jsx';
import InteractiveModal from '../components/InteractiveModal.svelte';
---
<!-- 该组件的 JS 将在页面加载开始时导入 -->
<InteractiveButton client:load />

<!-- 该组件的 JS 将不会发送给客户端,直到用户向下滚动到该组件在页面上可见时 -->
<InteractiveCounter client:visible />

<!-- 这个组件不会在服务端渲染, 但是在页面加载时将渲染在客户端 -->
<InteractiveModal client:only="svelte" />

组件渲染所需的 JavaScript 框架(React、Svelte 等)将与组件自己的 JavaScript 一起发送到浏览器。如果页面上的两个或多个组件使用相同的框架,则该框架将只发送一次。

可用激活指令

这里有几个适用于 UI 框架组件的激活指令:client:loadclient:idleclient:visibleclient:media={QUERY}client:only={FRAMEWORK}

查看指令参考页面获取这些激活指令的详细描述以及用法。

向框架组件传递 Props

你可以从 Astro 组件向框架组件传递 props:

src/pages/frameworks-props.astro

js
---
import TodoList from '../components/TodoList.jsx';
import Counter from '../components/Counter.svelte';
---
<div>
  <TodoList initialTodos={["learn Astro", "review PRs"]} />
  <Counter startingCount={1} />
</div>

使用 client:* 指令 传递给交互式框架组件的属性必须序列化:转换为适合在网络传输或存储的格式。然而,Astro 并不能序列化所有类型的数据结构。因此,在将属性传递给已激活的组件时,存在一些限制。

以下是支持的属性类型:普通对象、numberstringArrayMapSetRegExpDateBigIntURLUint8ArrayUint16ArrayUint32ArrayInfinity

传递给组件的不支持的数据结构,如函数,只能在组件的服务器渲染期间使用,不能用于提供交互性。例如,不能传递函数给已激活的组件,因为 Astro 无法从服务器传递函数来让客户端执行。

向框架组件传递子组件

在 Astro 组件中,你可以向框架组件传递子组件。每个框架都有自己的模式来引用这些子组件:React、Preact 和 Solid 均使用一个特殊的属性名 children,而 Svelte 和 Vue 则使用 <slot /> 元素。

src/pages/component-children.astro

html
---
import MyReactSidebar from '../components/MyReactSidebar.jsx';
---
<MyReactSidebar>
  <p>这里是一个带有一些文字和一个按钮的侧边栏。</p>
</MyReactSidebar>

另外你可以使用命名插槽来区分特定的子组件。

对于 React、Preact 和 Solid,这些插槽都会转换成顶级属性。使用 kebab-case 的插槽名会转换成 camelCase

src/pages/named-slots.astro

html
---
import MySidebar from '../components/MySidebar.jsx';
---
<MySidebar>
  <h2 slot="title">菜单</h2>
  <p>这里是一个带有一些文字和一个按钮的侧边栏。</p>
  <ul slot="social-links">
    <li><a href="https://twitter.com/astrodotbuild">Twitter</a></li>
    <li><a href="https://github.com/withastro">GitHub</a></li>
  </ul>
</MySidebar>

src/components/MySidebar.jsx

<aside>
  <header><slot name="title" /></header>
  <main><slot /></main>
  <footer><slot name="social-links" /></footer>
</aside>

我可以在我的框架组件中使用 Astro 组件吗?

任何 UI 框架组件都会成为该框架的一个“孤岛”。这些组件必须完全作为该框架的有效代码来编写,只使用它自己的导入和包。你不能在一个 UI 框架组件(如 .jsx.svelte)中导入 .astro 组件。

不过你可以在.astro组件内使用 Astro <slot/> 模式Astro 组件生成的静态内容作为子项传递给框架组件。

src/pages/astro-children.astro

html
---
import MyReactComponent from  '../components/MyReactComponent.jsx';
import MyAstroComponent from '../components/MyAstroComponent.astro';
---
<MyReactComponent>
  <MyAstroComponent slot="name" />
</MyReactComponent>

评论

评论加载中……