コンテンツにスキップ

コンポーネント

Astroコンポーネントは、あらゆるAstroプロジェクトの基本的な構成要素です。クライアントサイドのランタイムを持たない、HTMLのみのテンプレートコンポーネントです。Astroコンポーネントは、.astroという拡張子により判別できます。

Astroコンポーネントは非常に柔軟です。多くの場合、Astroコンポーネントは、ヘッダーやプロフィールカードのような、ページ上で再利用可能なUIを含むことになります。また、Astroコンポーネントには、SEO対策を容易にする共通の<meta>タグのコレクションのような、小さなHTMLのスニペットが含まれることもあります。Astroコンポーネントは、ページ全体のレイアウトを含められます。

Astroコンポーネントについて知っておくべきもっとも重要なことは、クライアント上でレンダリングされないということです。コンポーネントはビルド時またはサーバーサイドレンダリング(SSR)によりオンデマンドにレンダリングされます。コンポーネントのフロントマターの内部にJavaScriptコードを含められますが、それらはすべて、ユーザーのブラウザに送られる最終的なページからは取り除かれます。その結果、デフォルトではJavaScriptが含められることはなく、より高速なサイトが実現します。

Astroコンポーネントがクライアントサイドでインタラクティビティを必要とする場合は、標準のHTML<script>タグUIフレームワークのコンポーネントを追加できます。

Astroコンポーネントは、コンポーネントスクリプトコンポーネントテンプレートという2つの主要な部分で構成されています。それぞれのパーツは異なる役割を担いますが、この2つを組み合わせることで、使いやすさと、どんなものにも対応できる表現力を兼ね備えたフレームワークを提供しています。

src/components/EmptyComponent.astro
---
// コンポーネントスクリプト (JavaScript)
---
<!-- コンポーネントテンプレート (HTML + JS Expressions) -->

Astroでは、Astroコンポーネント内のコンポーネントスクリプトを識別するためにコードフェンス(---)を使用します。Markdownを書いたことがある方なら、すでにフロントマターという同様の概念に馴染みがあるかもしれません。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';
// 渡されたコンポーネントのprops(`<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ディレクティブもサポートしています。コンポーネントスクリプトで定義されたデータと値をコンポーネントテンプレートで使用することで、動的に作成されたHTMLを生成できます。

src/components/MyFavoritePokemon.astro
---
// コンポーネントスクリプトはここに書きます
import Banner from '../components/Banner.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- HTMLコメントに対応しています -->
{/* JSコメント構文も可能です */}
<Banner />
<h1>Hello, world!</h1>
<!-- propsやコンポーネントスクリプトの変数を使用します -->
<p>{title}</p>
<!-- `client:`ディレクティブの付いたUIフレームワークコンポーネントはハイドレートされます -->
<ReactPokemonComponent client:visible />
<!-- JSXと同じように、HTMLとJavaScriptの式を混ぜられます -->
<ul>
{myFavoritePokemon.map((data) => <li>{data.name}</li>)}
</ul>
<!-- テンプレートディレクティブを使って、複数の文字列やオブジェクトからクラス名を作成できます -->
<p class:list={["add", "dynamic", {classNames: true}]} />

コンポーネントは、再利用可能であり、他と組み合わせられるよう設計されます。コンポーネントの中に他のコンポーネントを使って、より高度なUIを構築できます。たとえば、Buttonコンポーネントを使ってButtonGroupコンポーネントを作成できます。

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

Astroコンポーネントは、propsを定義し、受け取れます。propsは、HTMLをレンダリングするためにコンポーネントテンプレートで利用できます。propsは、フロントマタースクリプトのグローバルな Astro.props で利用できます。

以下は、greetingnameのpropsを受け取るコンポーネントの例です。受け取るpropsは、グローバルな Astro.props オブジェクトから分割代入されることに注意してください。

src/components/GreetingHeadline.astro
---
// 使い方: <GreetingHeadline greeting="Howdy" name="Partner" />
const { greeting, name } = Astro.props;
---
<h2>{greeting}{name}</h2>

このコンポーネントをインポートして、他のAstroコンポーネント、レイアウト、ページでレンダリングする場合、属性としてこれらのpropsを渡せます。

src/components/GreetingCard.astro
---
import GreetingHeadline from './GreetingHeadline.astro';
const name = 'Astro';
---
<h1>グリーティングカード</h1>
<GreetingHeadline greeting="やぁ" name={name} />
<p>素敵な一日をお過ごしください!</p>

TypeScriptのProps型のインターフェイスでpropsを定義できます。Astroはフロントマター内のPropsインターフェイスを自動的に検出し、型の警告やエラーを出します。propsは、Astro.propsから分割代入する際に、デフォルト値を与えることもできます。

src/components/GreetingHeadline.astro
---
interface Props {
name: string;
greeting?: string;
}
const { greeting = "こんにちは", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

コンポーネントのpropsは、何も提供されない場合に使用するデフォルト値を指定できます。

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>フレッドについて</h2>
<p>ここでは、フレッドについて紹介します。</p>
</Wrapper>

このパターンはAstroレイアウトコンポーネントの基本です。HTMLコンテンツのページ全体を<Layout></Layout>タグで囲んでレイアウトコンポーネントに送り、そこで定義された共通のページ要素の中にレンダリングさせられます。

Astroコンポーネントは、名前付きスロットも使えます。これを利用すると、対応するスロット名を持つHTML要素のみをスロットの場所に渡せます。

スロットはname属性により名前を付けます。

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>

特定のスロットにHTMLコンテンツを渡すには、任意の子要素のslot属性によりスロット名を指定します。コンポーネントの他のすべての子要素は、デフォルトの(名前のない)<slot />に渡されます。

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">Copyright 2022</p>
</Wrapper>

<div>でラップせずに複数のHTML要素をコンポーネントの<slot/>プレースホルダーに渡すには、Astroの<Fragment/>コンポーネントslot=""属性を使用します。

src/components/CustomTable.astro
---
// ヘッドとボディのコンテンツ用の名前付きスロットプレースホルダーをもつカスタムテーブルを作成する
---
<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
---
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>

名前付きスロットは、コンポーネントの直接の子要素である必要があります。ネストされた要素を通して名前付きスロットを渡すことはできません。

スロットは他のコンポーネントに渡すことができます。たとえば、以下のネストされたレイアウトを作成する場合について考えます。

src/layouts/BaseLayout.astro
---
---
<html lang="ja">
<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
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout>
<slot name="head" slot="head"/>
<slot />
</BaseLayout>

これで、HomeLayoutに渡されたデフォルトのスロットとheadスロットは、BaseLayoutの親へと渡されます。

src/pages/index.astro
---
import HomeLayout from '../layouts/HomeLayout.astro';
---
<HomeLayout>
<title slot="head">Astro</title>
<h1>Astro</h1>
</HomeLayout>

スロットは、フォールバックコンテンツをレンダリングすることもできます。スロットに渡される子要素がない場合、 <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>

Astroは.htmlファイルをコンポーネントとしてインポートして使用したり、これらのファイルをページとしてsrc/pages/のサブディレクトリに設置することをサポートしています。フレームワークなしで構築された既存のサイトからコードを再利用したい場合や、コンポーネントに動的な機能が確実に入らないようにしたい場合はHTMLコンポーネントを利用するといいでしょう。

HTMLコンポーネントは有効なHTMLしか含むことができず、そのためAstroコンポーネントの主要機能が制限されます。

  • フロントマターやサーバーサイドのインポート、動的な記法をサポートしません
  • すべての<script>タグはバンドルされずに残され、is:inlineを持つ場合と同じように扱われます。
  • public/フォルダにあるアセットのみを参照できます。

📚 AstroプロジェクトでのUIフレームワークコンポーネントの使用方法について学びます。