2023.12.13
Astroでモーダルを実装する
この記事はAstro Advent Calendar 2023 13日目の記事です。
このポートフォリオ・ブログサイト(andmohiko.dev)はAstroで開発しています。ポートフォリオが増えてきたので各ポートフォリオの詳細をモーダルで表示したいと思いました。 今回はAstroでモーダルを実装していきます。
今回実装したコードはこちらから見ることができます。
Astroとは
Astroは、アイランドアーキテクチャを採用し、必要最小限のJavaScriptのみを使用することで高速なWebページを提供する現代的なWebフレームワークです。ReactやVueなど、複数のフロントエンドフレームワークと互換性があり、静的サイトジェネレータ(SSG)としても機能します。このフレームワークは、パフォーマンスの最適化と開発の柔軟性を重視するプロジェクトに特に適しています。
11/27にAstro 4.0のベータ版がリリースされ、最近勢いに乗っています。
実装
Astroのインストールとセットアップ
新しいプロジェクトを始めるにはターミナルで以下のコマンドを実行します。
$ pnpm create astro@latest
コマンドを実行すると以下のような構成のプロジェクトが作成されます。
my-astro-project/
├── public/ # 静的ファイル(画像、フォントなど)を保管するディレクトリ
├── src/
│ ├── components/ # Astroまたは他のフレームワークのコンポーネント
│ ├── layouts/ # ページのレイアウトテンプレート
│ ├── pages/ # 各ページのコンテンツとなるAstroファイル (.astro)
│ └── styles/ # CSSファイルやSCSSファイルなどのスタイル関連ファイル
├── astro.config.mjs # Astroの設定ファイル
├── package.json # プロジェクトの依存関係やスクリプトを定義するファイル
└── tsconfig.json # TypeScriptの設定ファイル(TypeScriptを使用する場合)
今回はモーダルのコンポーネントを作るのでcomponents配下にファイルを作っていきます。
Reactのインストール
次に、モーダル部分はReactで実装していくのでReactを追加します。
$ pnpm add @astrojs/react react react-dom
$ pnpm add @types/react -D
モーダルの実装
まずはモーダルのベースの部分を作っていきます。 こちらはいつものようにtsxで実装します。 CSSは割愛します。
// /components/BaseModal.tsx
import type { ReactNode } from 'react'
import styles from './style.module.scss'
type Props = {
children: ReactNode
isOpen: boolean
onClose: () => void
}
const BaseModal = ({ children, isOpen, onClose }: Props): ReactNode => {
return (
<>
<dialog open={isOpen} aria-modal="true" className={styles.baseModal}>
<button className={styles.closeButton} onClick={onClose}>
<img src="/images/svgs/close.svg" alt="close" />
</button>
{children}
</dialog>
<div className={styles.scrim} onClick={onClose} />
</>
)
}
export default BaseModal
こちらのBaseModal
コンポーネントを使ってポートフォリオの詳細を表示するモーダルもtsxで実装します。
// /components/WorkDetailModal.tsx
import type { ReactNode } from 'react'
import WorkDetail from '~/components/cards/WorkDetail/index.tsx'
import BaseModal from '~/components/modals/BaseModal'
import type { Work } from '~/types/work'
type Props = {
work: Work
isOpen: boolean
onClose: () => void
}
const WorkDetailModal = ({ work, isOpen, onClose }: Props): ReactNode => {
return (
<BaseModal isOpen={isOpen} onClose={onClose}>
<WorkDetail work={work} />
</BaseModal>
)
}
export default WorkDetailModal
次に、こちらのWorkDetailModal
を呼び出す元を用意します。今回はポートフォリオの一覧を表示するWorkList
というコンポーネントでWorkDetailModal
を呼ぶようにしました。
普段Reactで書くようにuseStateなども使えます。
// /components/WorkList.tsx
import { useState } from "react"
import type { ReactNode } from "react"
import type { Work } from "~/types/work"
import WorkCard from "~/components/cards/WorkCard"
import WorkDetailModal from "~/components/modals/WorkDetailModal"
import styles from './style.module.scss'
type Props = {
works: Work[]
}
const WorkList = ({ works }: Props): ReactNode => {
const [isOpen, setIsOpen] = useState<boolean>(false)
const [selectedWork, setSelectedWork] = useState<Work | null>(null)
const showDetail = (work: Work) => {
setSelectedWork(work)
setIsOpen(true)
console.log('showDetail')
}
return (
<>
<div className={styles.worksList}>
{works.map((work: Work) => (
<WorkCard key={work.id} work={work} onClick={() => showDetail(work)} />
))}
</div>
{isOpen && selectedWork && (
<WorkDetailModal work={selectedWork} isOpen={isOpen} onClose={() => setIsOpen(false)} />
)}
</>
)
}
export default WorkList
最後にこちらのWorkList
コンポーネントをページ側で使います。
astroファイルから自然にtsxファイルをimportすることができます。
// /pages/works.astro
---
import WorksLayout from '~/layouts/WorksLayout.astro'
import WorkList from '~/components/tables/WorkList/index.tsx'
import { getAllWorks } from '~/lib/microcms'
const works = await getAllWorks()
---
<WorksLayout>
<div class="works-container">
<h1 class="title">WORKS</h1>
<WorkList works={works} client:load />
</div>
</WorksLayout>
このとき、WorkList
コンポーネントにclient:load
と書くことで、ブラウザがページを完全にロードした後に、このコンポーネントにJavaScriptを適用することを指示できます。
こちらがAstroがパフォーマンス的に優れている理由で、JavaScriptを必要最低限に保つことでページの読み込み速度が速くなります。
さいごに
以上でAstroでモーダルを実装することができました。 必要なところでだけReactを使えるので開発体験も良くパフォーマンスの高いサイトを作ることができます。
Astroは今勢いに乗っているフレームワークで今後のアップデートも楽しみです。