组件

Astro 组件是 Astro 项目的基础构建块。它们是纯 HTML、无需客户端运行时的模板组件。

如果懂 HTML,你就已经有足够的知识来编写你的第一个 Astro 组件了

Astro 组件的语法是 HTML 的超集。该语法设计地让所有拥有编写 HTML 或 JSX 经验的人都感到熟悉,并增加包括对组件和 JavaScript 表达式的支持。你可以通过文件扩展名 .astro 来创建新的 Astro 组件。

Astro 组件非常灵活的。通常情况下,Astro 组件会包含一些可在页面中复用的 UI,如 header 或简介卡。在其他时候,Astro 组件可能包含一个较小的 HTML 片段,像是常见的使 SEO 更好的 <meta> 标签集合。Astro 组件甚至可以包含整个页面布局。

Astro 组件中最重要的一点是,它们在构建过程中会被渲染成 HTML。即使你在组件内运行 JavaScript 代码,它也会抢先一步运行从呈现给用户的最终页面中剥离出来。其最终使得网站变得更快,且默认不用任何 JavaScript。

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

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

你也可以在其他组件中使用组件以建立更多更先进的 UI。例如 Button 组件可以被用来创建 ButtonGroup 组件,像是这样。

src/components/ButtonGroup.astro
---
import Button from './Button.astro';
---
<div>
  <Button title="按钮 1" />
  <Button title="按钮 2" />
  <Button title="按钮 3" />
</div>

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

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

  • 导入其他 Astro 组件
  • 导入其他框架组件,如 React
  • 导入数据,如 JSON 文件
  • 从 API 或数据库中获取内容
  • 创建你要在模板中引用的变量
src/components/MyComponent.astro
---
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 指令。在组件脚本中定义的数据和值(在页面构建时)可以在组件模板中使用,以产生动态创建的 HTML。

src/components/MyFavoritePokemon.astro
---
// 你的组件脚本在这!
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
---
<!-- 支持 HTML 注释! -->

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

<!-- 在组件脚本中使用参数或其他变量: -->
<p>我最喜欢的宝可梦是:{Astro.props.title}</p>

<!-- 包括其他带有 `client:` 指令的激活组件: -->
<ReactPokemonComponent client:visible />

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

<!-- 使用模板指令并根据字符串或对象来生成 class 名: -->
<p class:list={["add", "dynamic", {classNames: true}]} />

你可以在 Astro 组件的 frontmatter 组件脚本内定义局部 JavaScript 变量。然后你就可以在组件的 HTML 模板中使用 JSX 表达式插入这些变量!

在 HTML 中可以通过大括号使用局部变量:

src/components/Variables.astro
---
const name = "Astro";
---
<div>
 <h1>你好 {name}</h1>  <!-- 输出`<h1>你好 Astro!</h1>` -->
</div>

这些局部变量可以用大括号来传递属性值给 HTML 元素和组件。

src/components/DynamicAttributes.astro
---
const name = "Astro";
---
<h1 class={name}>支持属性表达式</h1>

<MyComponent templateLiteralNameAttribute={`MyNameIs${name}`} />

局部变量可以在类似 JSX 的函数中使用,产生动态生成的 HTML 元素。

src/components/DynamicHtml.astro
---
const items = ["Dog", "Cat", "Platypus"];
---
<ul>
  {items.map ((item) => (
    <li>{item}</li>
  ))}
</ul>

Astro 可以使用 JSX 逻辑运算符和三元表达式有条件地显示 HTML。

src/components/ConditionalHtml.astro
---
const visible = true;
---
{visible && <p>Show me!</p>}

{visible ? <p>Show me!</p> : <p>Else show me!</p>}

你也可以通过将一个变量设置为HTML标签名称或组件导入来使用动态标签。

src/components/DynamicTags.astro
---
import MyComponent from "./MyComponent.astro";
const Element = 'div'
const Component = MyComponent;
---
<Element>Hello!</Element> <!-- 渲染为 <div>Hello!</div> -->
<Component /> <!-- 渲染为 <MyComponent /> -->

Astro 组件模板可以渲染多个元素,而无需像 JavaScript 或 JSX 将所有内容包装在单个 <div><> 中。

src/components/RootElements.astro
---
// 多元素模板
---
<p>无需将元素包裹在一个元素中。</p>
<p>Astro 支持在模板中使用多根元素。</p>

但是,当使用类似表达式动态创建多元素时,你应该像在 JavaScript 或 JSX 中那样将这些多个元素包装在一个片段 中。Astro 支持使用 <Fragment> </Fragment><> </> 速记。

---
const items = ["狗", "猫", "鸭嘴兽"];
---
<ul>
  {items.map ((item) => (
    <>
      <li>红色的{item}</li>
      <li>蓝色的{item}</li>
      <li>绿色的{item}</li>
    </>
  ))}
</ul>

在使用 set:* 指令时,片段也用于避免使用包装元素,如下所示:

---
const htmlString = '<p>原始 HTML 内容</p>';
---
<Fragment set:html={htmlString} />

Astro 组件的语法是 HTML 的超集。它的设计使得任何有 HTML 或 JSX 经验的人都感到熟悉,但 .astro 文件和 JSX 之间有几个关键的区别。

在 Astro 中,所有 HTML 属性都使用标准的 kebab-case 格式,而不是 JSX 中使用的 camelCase。这甚至适用于 class,而 React 不支持。

example.astro
<div className="box" dataValue="3" />
<div class="box" data-value="3" />

在 Astro 中,你可以使用标准的 HTML 注释,而 JSX 会使用 JavaScript 风格的注释。

example.astro
<!-- .astro 文件中可以使用 HTML 注释 -->

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

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

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

你还可以使用 TypeScript 导出 Props 类型接口来定义参数。Astro 将自动选择任何导出的 props 接口,并为你的项目提供类型警告/错误提示。当从 Astro.props 解构时,这些参数也可以被赋予默认值。

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

你也可以通过导出 Props 类型接口,用 TypeScript 定义来参数。Astro 会自动接收任何导出的 Props 接口,并为你的项目提供类型警告/错误。这些道具也可以在从 Astro.props 解构时给出默认值。

src/components/GreetingHeadline.astro
---
export interface Props {
  name: string;
  greeting?: string;
}

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

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

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

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

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

src/components/Wrapper.astro
---
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 />  <!-- 子项会在这 -->
  <Footer />
</div>
src/pages/fred.astro
---
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 内容可以用 <Layout></Layout> 标签包裹并传递到 Layout 组件以在常见页面元素中呈现。

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

src/components/Wrapper.astro
---
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 name="after-header"/>  <!--  带有 `slot="after-header"` 属性的子项在这 -->
  <Logo />
  <h1>{title}</h1>
  <slot />  <!--  没有 `slot` 或有 `slot="default"` 属性的子项在这 -->
  <Footer />
  <slot name="after-footer"/>  <!--  带有 `slot="after-footer"` 属性的子项在这 -->
</div>
src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="弗雷德的页面">
  <img src="https://my.photo/fred.jpg" slot="after-header">
  <h2>关于弗雷德的一切</h2>
  <p>这里有一些关于弗雷德的资料。</p>
  <p slot="after-footer">版权所有 2022</p>
</Wrapper>

在要传递给组件相应的 <slot name="my-slot"/> 占位符的子元素上使用 slot="my-slot" 属性。

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

src/components/Wrapper.astro
---
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>

组件模板内部也支持 CSS <style> 标签。

它们可用于设置组件样式,并且所有样式规则都自动仅限用于组件范围内,以防止大型应用程序中的 CSS 冲突。

src/components/StyledHeading.astro
---
// 你的组件脚本在这!
---
<style>
  /* 仅限该组件范围内,页面上的其他 H1 保持不变 */
  h1 { color: red }
</style>

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

📚 有关应用样式的更多信息,请参阅我们的样式指南

在不使用使用框架组件(React、Svelte、Vue、Preact、SolidJS、AlpineJS、Lit)或 Astro 集成(例如 astro-XElement)时,你可以在 Astro 组件模板中使用 <script> 标签使得该 JavaScript 可以在浏览器中使用。

默认情况下,<script> 标签由 Astro 处理:

  • 任何导入都将被捆绑,允许你导入本地文件或 node 模块。
  • 处理后的脚本将通过 type="module" 注入你页面的<head>
  • 全面支持 TypeScript 包括导入 TypeScript 文件
  • 如果你的组件在页面上多次使用,则脚本标签将只包含一次。
<script>
  // 处理!捆绑!TypeScript 支持!可以使用 ESM 导入,甚至也适用于 npm 包。
</script>

要避免捆绑脚本,你可以使用 is:inline 属性:

<script is:inline>
  // 将会完全按照写好的内容呈现在 HTML 中!
  // ESM 导入将不会相对于文件进行解析。
</script>

上述方法可以自由搭配组合,也可以在同一个 .astro 文件多次使用 <script> 标签。

📚 请参阅我们的指令参考页面以获取有关 <script> 标签上可用指令的更多信息。

什么时候用? 如果你的 JavaScript 文件处于 public/ 中时。

请注意,当你使用下面提到的 import 方法时,该方法会跳过由 Astro 提供的 JavaScript 处理、捆绑和压缩。

// 绝对路径
<script is:inline src="/some-external-script.js"></script>

什么时候用? 如果你的外部脚本位于 src/ 中,并且它支持 ESM 模块类型时。

Astro 检测到这些 JavaScript 将在客户端导入,然后自动构建、压缩并将 JS 添加到页面中。

// ESM 导入
<script>
  import './some-external-script.js';
</script>

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

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

  • 他们不支持 frontmatter、服务器端导入或动态表达。
  • 无法捆绑任何 <script> 标签,就像他们有 is:inline 一样。
  • 它们只能引用 public/ 文件夹中的资产

📚 阅读Astro 的内置组件

📚 了解如何在你的 Astro 项目中使用 JavaScript 框架组件