Remixプロジェクトの構成
実際にページの実装に入る前に、簡単にプロジェクト内のファイルの役割を確認していきましょう。Remixフレームワークが扱う主要なファイル・フォルダを図3にまとめました。
app/routes
フォルダには、Reactコンポーネントが export default
でエクスポートされているファイルを配置します。Remixはファイルシステムベースのルーティング機構(file-system based router)を備えているため、ファイル名がそのまま、 export default
されたコンポーネントを表示するためのURLになります。図3では、app/routes/index.jsx
が配置されていますが、ここに記述したコンポーネントは、ルートパス( /
)にアクセスしたときに表示されることとなります。後述しますが、 app/routes
内にフォルダを作って、その中にコンポーネントのファイルを定義していけば、URLのパスを深くしていくことができます。
app/entry.client.jsx
は、ブラウザ側でのコンポーネント描画である、クライアントサイドレンダリング(CSR)のエントリーポイント(起点)となるファイルです。ブラウザ側でのアプリケーションの実行はここから始まります。Reactに詳しい人は、 ReactDOM.hydrateRoot()
を行う場所とご理解ください。
app/entry.server.jsx
は、サーバー側でのコンポーネント描画であり、HTMLの生成処理でもある、サーバーサイドレンダリング(SSR)のエントリーポイントとなるファイルです。サーバー側での処理がここから始まるため、今回紹介するファイル・フォルダの中では一番早いタイミングで実行されることになります。
app/root.jsx
は、HTMLのベースとなるコンポーネントを記述したファイルです。 <html>
や <head>
や <body>
に何を埋め込むのかを決定する役割を持つため、ブラウザから見ると最も重要なファイルの一つとなります。SSRもCSRも、このファイルを元にしてレンダリングを行います。
public
フォルダには、ブラウザに配信する静的ファイルが格納されます。画像などに加えて、ビルド済みのJavaScriptファイルが public/build
として配信されたりもします。
最後に、 remix.config.js
はRemixの設定ファイルです。Remixのビルド先のフォルダを変更したり、 app/routes
のファイルパスとは違ったパスのURLを定義するときにも利用できます。
基本的には、Remixはこれらのファイルの組み合わせで成立しています。UIライブラリをセットアップする際などは entry
や root
のファイルを設定することもありますが、多くの場合は app/routes
を起点にページ実装を進めていくことになると覚えておくとよいでしょう。
サーバーで取得したデータを画面に表示する
それでは、実際にhackernews-viewerを編集してみましょう。まずは、通信用の便利関数を用意しておきます(リスト3)。
/** Hacker Newsの人気・最新の記事500件の記事IDを取得する */
export async function getTopStories() {
return fetch("https://hacker-news.firebaseio.com/v0/topstories.json").then(
(res) => res.json()
);
}
/** 記事の詳細データを取得する */
export async function getItem(id) {
return fetch(https://hacker-news.firebaseio.com/v0/item/${id}.json
).then(
(res) => res.json()
);
}
Remixのフレームワーク内では、サーバーサイドでもクライアントサイドでも、特に区別することなくWeb標準のFetch API(主に fetch()
関数のことです)を利用して通信できます。
データに着目すると、 getTopStories()
で取得できるのは記事のIDの配列だけです。そのため、タイトル等を取得するためには、各IDを getItem()
に渡して、記事データを入手する必要があります。
それでは、リスト3の関数を使って、人気・最新の記事20件を表示するUIを作成してみましょう。 app/routes/top20.jsx
を作成して、リスト4のコードを記述します。CSSファイルはサンプルコードに入っているので、手元で動かしてみる場合はご利用ください。
import { useLoaderData, Link } from "@remix-run/react"; import { getItem, getTopStories } from '~/utils/hackerNews.server'; import stylesUrl from '~/style/top20.css'; // (5) このページで読み込むCSSファイルを指定する export const links = () => { return [{ rel: "stylesheet", href: stylesUrl }]; }; // (1) サーバーサイドでデータを取得する export const loader = async () => { // 500件のデータを取得する const top500Ids = await getTopStories(); // 上位20件のIDだけに絞り込む const top20Ids = top500Ids.slice(0, 20); // 上位20件の記事データを取得する const top20 = await Promise.all(top20Ids.map((id) => getItem(id))); // 記事データのIDとタイトルだけに絞り込む const top20Summary = top20.map((item) => ({ id: item.id, title: item.title, })); // idとtitleのみのオブジェクトが20件入った配列を返す return top20Summary; }; ///top20
のURLで表示するページのコンポーネント export default function Top20Route() { // (2) loaderで取得済みのデータを取り出す const data = useLoaderData(); return ( <div> <header> <h1>Hacker News Viewer</h1> </header> <div id="container"> <div id="sidebar"> <h2>Top 20</h2> <nav> <ul> {data.map((item) => ( <li key={item.id}> {/* (3) タイトルをリンクにする */} <Link to={`/top20/${item.id}`
}>{item.title}</Link> </li> ))} </ul> </nav> </div> <main> {/* (4) */} <div>本文をここに表示する</div> </main> </div> </div> ); }
(1)で定義した loader()
関数は、画面の表示前にサーバーサイドでデータを読み込んでおくために、フレームワークから呼び出されるものです。リスト3の便利関数を使いながら、20件分の記事データの配列を作り出しています。 loader()
で作成したデータは、コンポーネントを描画し始めたタイミングで呼び出された(2)の useLoaderData()
で取り出すことができ、画面の描画に利用できます。記事データの配列は、リストとして表示し、(3)でリンクにしました。後ほど、このリンク先のページも作成します。リンクを作成するために使用した <Link>
は、Remixでシングルページアプリケーションとしての画面遷移を行うためのコンポーネントです。リンクをクリックしたら、(4)の領域に本文を表示する予定ですが、少し特殊な仕組みを使う予定なので、今のところは仮置きのテキストを表示するだけにしておきます。
余談ですが、リスト4ではアクセシビリティに配慮したマークアップを心がけてみました。(3)は記事一覧のメニューとして扱いたいので <nav>
要素で囲み、(4)は本文なので <main>
要素で囲んであります。明確に役割があるものについては、 <div>
要素ではなく、こういった要素を使うことも意識するとよいでしょう。HTMLの文書構造をきれいにした後は、(5)の links
にCSSを登録することで、 <head>
要素内に <link>
要素を生成し、スタイルを有効にして見た目を整えています。
では、実際に動かしてみましょう。ターミナルで npm run dev
を実行して、 http://localhost:3000/top20
にアクセスします(図4)。
記事のリストをメニューとして表示できました。ちゃんと20件のリンクが並んでいますね。リンクをクリックしてみると、 404 Not Found
が表示されますが、まだ該当のページを作成していないので、期待通りの挙動です。
まとめ
今回はRemixのプロジェクト構成を解説し、データ読み込みの方法を簡単に解説しました。次回は <main>
の中身だけを切り替えられるNested Routes機能の解説を中心に、Remixの基本を解説します。