☺️

Remix入門【はじめからそうやって教えてくれればいいのに!】

2024/07/23に公開

はじめに

この記事の内容は、以下の動画でも解説しています。アニメーションでわかりやすくなっているので、ぜひ見てみてください。他にもWebに関する解説動画を投稿しているので、気になる人はチャンネル登録よろしくお願いします!

https://youtu.be/jLBuwRKG6tM?si=Mbzv1LAPUOfqaU4-

Remixとは?

Remix とは、公式サイト によると、React ベースのフルスタックの Web フレームワーク のことです。

Remix is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.

(訳)Remixはフルスタックのウェブフレームワークで、ユーザーインターフェイスに集中し、ウェブスタンダードを通して、高速で、スムーズで、弾力性のあるユーザーエクスペリエンスを提供します。

remix.run

...と言っても、これだけだとよく分からないですよね。そこで、今回の記事では「Remix とは何か?」を簡単に理解できるよう、Remix の基本的な機能について解説していきます。

準備

まず、Remix を動かす環境を準備します。

Remixプロジェクトを作成するには、npx create-remix@latest を実行します。その後、プロジェクトに関していくつか質問されるので、次のように答えます。

  • プロジェクトの作成場所: ./remix-tutorial
  • git repository を初期化するか: 「No」(今回はどちらでもOK)
  • npm でパッケージをインストールするか: 「Yes」

セットアップが完了したら、ローカルサーバーを起動しましょう。

ターミナル
cd remix-tutorial
npm run dev

ブラウザで「http://localhost:5173」にアクセスすると、次のような画面が表示されるはずです。

これで、準備は完了です。さっそく始めていきましょう!

Remixの基本

Remix には、代表的な機能が5つあります。一つずつ紹介していきます。

Outlet

まず1つ目は「Outlet」と呼ばれる機能です。

Outlet とは、親ルート内において、子ルートがレンダリングされる場所を示すために使われるコンポーネントのことです。

Renders the matching child route of a parent route.

remix.run

Outlet について理解するために、app/root.tsx の中身を一旦すべて削除して、次のコードに書き換えましょう。

app/root.tsx
import { Links, Meta, Scripts, Outlet } from "@remix-run/react";
import "./tailwind.css";

export default function App() {
  return (
    <html lang="ja">
      <head>
        <meta charSet="utf-8" />
        <Meta />
        <Links />
      </head>
      <body>
        <main className="p-6">
          {/* <Outlet /> */}
        </main>
        <Scripts />
      </body>
    </html>
  );
}

上記のコードの <main> の中では、意図的に <Outlet /> というコンポーネントがコメントアウトされています。このコードを保存したら、画面の表示はどうなっているでしょうか?

答えは、次のように真っ白な画面が表示されます。

これは何故でしょうか?

実は Outlet が無いと、親ルートは子ルートを「どこに配置すれば良いのか」分からなくなってしまうのです。

そのため、Outlet を使って、どこに子供を表示するべきか教えてあげる必要があります。次のようなイメージです。

実際にやってみましょう!次のように、 <Outlet /> をアンコメントするだけです。

root.tsx
  import { Links, Meta, Scripts, Outlet } from "@remix-run/react";
  import "./tailwind.css";
  
  export default function App() {
    return (
      <html lang="ja">
        <head>
          <meta charSet="utf-8" />
          <Meta />
          <Links />
        </head>
        <body>
          <main className="p-6">
+            <Outlet />
-           {/* <Outlet /> */}
          </main>
          <Scripts />
        </body>
      </html>
    );
  }

すると、ブラウザでは、元どおり「Welcome to Remix」と表示されます。

このように、Outlet コンポーネントを使うことで、親ルートに対して、子ルートがレンダリングされる場所を示すことができます。

meta

2つ目に紹介するのは「meta関数」です。

meta関数とは、HTML内の メタデータ を変更するために使われる関数のことです。

The meta export allows you to add metadata HTML tags for every route in your app.

remix.run

そもそも「メタデータ」とは何でしょうか?

メタデータは、直訳すると「データについてのデータ」という意味です。簡単に言うと、Webページに関する情報を提供するデータのことを指します。

例えば、Googleで「Remix」と検索する場合を考えてみましょう。

上記の画像のように、赤枠の部分は「タイトル」と呼びます。そして青枠の部分は「メタディスクリプション」と呼ばれます。このタイトルとメタディスクリプションのように、データを説明するためのデータのことを「メタデータ」と呼びます。

Remix では、このメタデータを簡単に変更できるように、「meta関数」というものが用意されています。では、試しにメタデータを変更してみましょう。

app/routes/_index.tsx
  export const meta: MetaFunction = () => {
    return [
+      { title: "ずんだブログ" },
-      { title: "New Remix App" },
+      { name: "description", content: "ずんだもんのブログなのだ" },
-      { name: "description", content: "Welcome to Remix!" },
    ];
  };

上記のように変更すると、タイトルは「ずんだブログ」、メタディスクリプションは「ずんだもんのブログなのだ」に設定できます。

検証ツールを使って確認してみると、変更が反映されていることが確認できます。

このように、meta関数を使うことで、各ページのメタデータを簡単に変更することができます。

loader

3つ目は「loader関数」です。

loader関数とは、レンダリング時にデータを取得するために、サーバ側で実行される関数のことです。

Each route can define a loader function that provides data to the route when rendering.

remix.run

例えば、トップページに、投稿(Post)情報のタイトルをリスト形式で表示することを考えてみましょう。

この場合、レンダリング時に、投稿一覧のデータを DB から取ってくる必要があります。この データ取得 の仕事を行うのが、「loader関数」です。図にすると、次のようなイメージです。

loader関数は、サーバーでのみ実行される関数で、ページが表示される前に自動で実行されます。実際に、使ってみましょう!

使い方は簡単です。次のような関数を定義するだけです。

app/routes/_index.tsx
import { json } from "@remix-run/node";

export const loader = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
  const data = await response.json();
  console.log(data)
  return json({ posts: data });
};

必ず名前に loader と付けて、先頭に export を付けます、そうすると、loader関数になります。今回は、loader関数の中で、JSONPlaceholder という無料のフェイク API を利用しています。

ターミナルを確認すると、次のように表示されているはずです。(loader関数はサーバー側で実行される関数なので、ターミナルで確認します)

このように、loader関数を使うことで、サーバーレンダリング時に、データを簡単に取得することができます。

取得したデータを画面上に表示するには?(useLoaderDataについて)

「せっかくloader関数を使ってデータを取得できたんだから、ブラウザ上でもpostデータを表示させたい!」って人は、以下のようにコンポーネント内でuseLoaderDataというフックを使う必要があります。

const posts = useLoaderData<typeof loader>();

useLoaderData について詳しく知りたい方は、記事の冒頭でも紹介した Youtube 動画を是非見てみてください。useLoaderData についても、詳しく解説しています。
https://youtu.be/jLBuwRKG6tM?si=Mbzv1LAPUOfqaU4-

Dynamic Segments

4つ目は「Dynamic Segments(ダイナミックセグメンツ)」です。

Dynamic Segments とは、動的に変わるURLのパス部分をマッチさせて、その値をコード上で使えるようにする機能のことです。

Usually your URLs aren't static but data-driven. Dynamic segments allow you to match segments of the URL and use that value in your code. You create them with the $ prefix.

remix.run

例えば、投稿詳細ページなどのように、URLのパスの一部分を「1」「2」「3」と、動的に変更させたい場合がありますよね。次のようなイメージです。

そこで登場するのが、この Dynamic Segments です。具体的には、ファイルを作成する際に $ という記号を使用します。

実際に、app/routes の中に posts.$postId.tsx という名前のファイルを作ってみましょう。

(補足)ドット分割について

ファイルを作成するときに、$ の直前で使われている . が何なのか気になった人もいると思います。これは「ドット分割(Dot Delimiters)」と呼ばれるもので、簡単に言うと、URLの / を表します。

詳細は以下の公式ドキュメント読んでみてください。

そして、作成したファイルに簡単なコンポーネントを定義します。

app/routes/posts.$postId.tsx
export default function Post() {
  return (
    <div>
      <h1 className="font-bold text-3xl">投稿詳細</h1>
    </div>
  );
}

コードを保存したら、ブラウザで以下のURLを開いてみてください。

いずれのページにも、「投稿詳細」という文字が、ちゃんと表示されていることが分かります。

このように、Dynamic Segmentsを使うと、動的に変わるURLのパス部分をマッチさせることができます。

action

最後は、「action関数」についてです。

action関数とは、データの変更などを行うために、サーバー側で実行される関数のことです。

A route action is a server only function to handle data mutations and other actions. If a non-GET request is made to your route (DELETE, PATCH, POST, or PUT) then the action is called before the loaders.

remix.run

先ほど紹介した3つ目の機能である「loader関数」は覚えているでしょうか?action関数は、このloader関数と似た機能を持っています。

少し復習ですが、loader関数はデータを取得するために使われていて、レンダリング時に実行されるのでしたよね。これに対してaction関数は、データを更新するために 使われる関数です。つまり、POSTやDELETE、PUT、PATCH リクエストが送られた時に実行されます。

実際に使ってみましょう。まず、app/routes の中に、posts.new.tsx というファイルを作成します。そこに、次のようなコードを追加します。

app/routesposts.new.tsx
import { ActionFunctionArgs } from "@remix-run/node";
import { Form, redirect } from "@remix-run/react";

export const action = async ({ request }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const title = formData.get("title");
  const body = formData.get("body");
  console.log(title, body);
  return redirect("/");
};

export default function New() {
  return (
    <div>
      <h1 className="font-bold text-3xl">投稿作成</h1>
      <Form method="post">
        <input type="text" name="title" className="border-2 block" />
        <textarea name="body" className="border-2 block" />
        <button type="submit" className="border-2 p-2">
          作成
        </button>
      </Form>
    </div>
  );
}

まず、<Form> から説明すると、これは Remix が用意しているコンポーネントです。これを使うことで、通常の HTMLの <form> よりも簡単にフォーム処理ができます(※詳しくはこちらの公式サイトを参照)。そして、このフォームの method には、「post」を指定しています。これにより、フォームが送信されると action 関数が呼ばれるようになります。

次に、action関数の中身を見てみましょう。action関数は、request オブジェクトを引数に取り、送られたフォームのデータを取得しています。今回は、簡単な例として、このフォームデータからタイトルと本文を抽出して、ターミナルで表示させています。そして、最後にトップページにリダイレクトするようにしています。

以下の手順で、新しい投稿を作成してみましょう。

  • ブラウザで「http://localhost:5173/posts/new」にアクセス
  • フォームに適当なタイトルと本文を入力して、「作成」ボタンをクリック

すると、ターミナルに Remix action関数のテスト と表示された後に、トップページにリダイレクトされるはずです。

このように、action関数を使うことで、フォームのデータを受け取り、サーバー側で処理を行うことができます。

おわりに

この記事の他にも、「100秒で理解する」というシリーズを書いています。よかったら見てください!

参考文献

Discussion