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は今勢いに乗っているフレームワークで今後のアップデートも楽しみです。