コンテンツにスキップ

Remix@フレームワーク

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. Remixとは

Reactパッケージを使用したフレームワークである。

loader ---> component ---> action の順に処理が実行される。

  1. loaderは、レンダリング前にAPIからデータを取得する。
  2. componentは、レンダリング処理を実行する。
  3. actionは、APIリクエストやブラウザ操作に応じて、データを変更する。actionは、バックエンドのコントローラーと同様にクエリストリングやリクエストのコンテキストを受信する。


02. Remixの仕組み

loader

▼ loaderとは

loader (ユーザーがloaderと命名した関数) は、レンダリング前にAPIからデータを取得する。

各エンドポイントごとに定義できる。

DBにクエリを送信し、データを取得できる。

認証処理がある場合、loader関数の前に実行する必要がある。

*実装例*

import {json} from "@remix-run/node";
import {useLoaderData} from "@remix-run/react";

export const loader = async () => {
  return json({
    posts: [
      {
        slug: "my-first-post",
        title: "My First Post",
      },
      {
        slug: "90s-mixtape",
        title: "A Mixtape I Made Just For You",
      },
    ],
  });
};

▼ useLoaderData

loader関数で取得したデータを出力できる。

*実装例*

import {json} from "@remix-run/node";
import {useLoaderData} from "@remix-run/react";

export const loader = async () => {
  return json({
    posts: [
      {
        slug: "my-first-post",
        title: "My First Post",
      },
      {
        slug: "90s-mixtape",
        title: "A Mixtape I Made Just For You",
      },
    ],
  });
};

export default function Posts() {
  const {posts} = useLoaderData<typeof loader>();
  return (
    <main>
      <h1>Posts</h1>
    </main>
  );
}


component

componentは、レンダリング処理を実行する。


action

actionは、APIリクエストやブラウザ操作に応じて、データを変更する。

バックエンドのコントローラーと同様にクエリストリングやリクエストのコンテキストを受信する。

componentを同じファイルに実装する以外に、.serverディレクトリに切り分ける方法もある。

import type { ActionFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";

import { TodoList } from "~/components/TodoList";
import { fakeCreateTodo, fakeGetTodos } from "~/utils/db";

// loaderでレンダリング前にデータを取得する
export async function loader() {
  return json(await fakeGetTodos());
}

// componentで、レンダリング処理を実行する
export default function Todos() {

  // useLoaderDataでloaderによる取得データを出力する
  const data = useLoaderData<typeof loader>();

  // Todoリストを出力する
  return (
    <div>
      <TodoList todos={data} />
      <Form method="post">
        <input type="text" name="title" />
        {/*
          Create Todoボタンを設置する
          同一ファイルのactionをコールする。
        */}
        <button type="submit">Create Todo</button>
      </Form>
    </div>
  );
}

// actionで、受信したリクエストに応じた処理を実行する
export async function action({request}: ActionFunctionArgs) {
  const body = await request.formData();
  const todo = await fakeCreateTodo({
    title: body.get("title"),
  });
  return redirect(`/todos/${todo.id}`);
}


03. ディレクトリ構成

構成

.
├── app/
│  ├── components/ # ユーザー定義のRemix component
│  │
│  ├── models/ # モデルのCRUD処理
│  │
│  ├── routes/ # ルーティングとレンダリングの処理
│  │
│  ├── utils/ # 汎用的な関数
│  │
│  ├── entry.client.tsx
│  ├── entry.server.tsx
│  └── root.tsx

├── prisma/ # モデルの定義
...


root.tsx

アプリケーションのルートである。

linkタグ、metaタグ、scriptタグを定義する。


entry.client.tsx

マークアップファイルのハイドレーション処理のエントリーポイントである。


entry.server.tsx

レスポンス作成処理のエントリーポイントである。

RemixServerで設定を変更できる。


04. ルーティング

UIとAPI

Rえmixでは、ブラウザまたはAPIへのルーティングが区別されず、両方を兼ねている。

ただし、ファイル名によって区別することもできる。

app/routes/api.<任意のパス>ファイルまたはapp/routes/api/<任意のパス>ファイルを作成する。

このファイルの処理は、APIとして処理される。


ドッド分割

_index.tsx

ルートパスになる。

app/                        # URLパス
├── routes/
│   ├── _index.tsx          # /
└── root.tsx

<ルート以降のパス>.tsx

ルート以降のパスを設定する。

app/                        # URLパス
├── routes/
│   ├── _index.tsx          # /
│   ├── home.tsx            # /home
│   ├── home.contents.tsx   # /home/contents
└── root.tsx

*実装例*

// <ルート以降のパス>._index.tsx
export default function Foo() {
  // 返却するHTML要素
  return (
    <main>
      <h1>Foo</h1>
    </main>
  );
}

<ルート以降のパス>.<変数>.tsx (動的セグメント)

動的にURLを決定する。

URLに規則性があるようなページに適する。

app/                        # URLパス
├── routes/
│   ├── _index.tsx          # /
│   ├── home.tsx            # /home
│   ├── home.contents.tsx   # /home/contents
│   ├── user.$id.tsx        # /user/{任意の値}
└── root.tsx

*実装例*

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

以下のURLでページをレンダリングできる。

  • /posts/1
  • /posts/2
  • /posts/3

▼ 子の_<ルート以降のパス>.tsx

子のファイル名にプレフィクスとして _ (パスレスルート) をつける。

これにより、親からレイアウトを引き継ぎつつ、パスは引き継がない。

*実装例*

_home.auth.tsxファイルは、親のhome.tsxファイルのレイアウトを引き継いでいる。

しかし、/home/authパスではなく、/authパスになる。

app/                       #  URLパス                   引き継ぐレイアウト
├── routes/
│   ├── _index.tsx         #  /                        root.tsx
│   ├── home.tsx           #                           root.tsx
│   ├── home._index.tsx    #  /home                    home.tsx
│   ├── home.contents.tsx  #  /home/contents           home.tsx
│   ├── home_.mine.tsx     #  /home/mine               root.tsx
│   ├── _home.auth.tsx     #  /auth                    home.tsx # 親からパスを引き継がない
│   ├── user.$id.tsx       #  /user/{任意の値}          root.tsx
│   ...

└── root.tsx

▼ 親の_<ルート以降のパス>.tsx (親がパスレスルート)

親のファイル名にプレフィクスとして _ (パスレスルート) をつける。

*実装例*

_auth.<任意の名前>.tsxファイルは、親の_auth.tsxファイルのレイアウトを引き継いでいる。

しかし、全てのファイルのURLにauthが含まれない。

app/                               # URLパス
├── routes                         #
│   ├── _auth.tsx                  #
│   ├── _auth.login.tsx            # /login
│   ├── _auth.password.reset.tsx   # /password/reset
│   ├── _auth.register.tsx         # /register
...
// _auth.tsxファイル
import {Outlet} from "@remix-run/react";

import {SiteFooter, SiteHeader} from "~/components";

export default function AuthCommon() {
  return (
    <div className="grid grid-rows-[auto_1fr_auto] h-dvh">
      <SiteHeader />
      {/* Outletに子 (login、password.reset、register) を出力する */}
      <Outlet />
      <SiteFooter />
    </div>
  );
}


ディレクトリ分割

ディレクトリ名がパスとして認識される。


05. componentの種類

ユーザー定義

Remixがcomponentであることを認識するために、名前の先頭を大文字する。


Form

formタグをレンダリングする。

action値を省略した場合、フォームの入力データは他に送信されず、そのコンポーネント内のみで処理される。

action値を/foos?indexパスとした場合、routes/foos/index.jsxファイルにデータを送信する。

一方で、action値を/foosパスとした場合、routes/foos.jsxファイルにデータを送信する。

*実装例*

ここではaction値を省略している。

import {Form} from "@remix-run/react";

function NewEvent() {
  return (
    <Form action="/events" method="post">
      <input name="title" type="text" />
      <input name="description" type="text" />
    </Form>
  );
}


Meta

Webページのmetaタグ (Webサイト名、説明など) をレンダリングする。

import {Meta} from "@remix-run/react";

export default function Root() {
  return (
    <html>
      <head>
        <Meta />
      </head>
      <body></body>
    </html>
  );
}


Outlet

親ページ内に子ページをレンダリングする。

import {Outlet} from "@remix-run/react";

export default function SomeParent() {
  return (
    <div>
      <h1>Parent Content</h1>

      <Outlet />
    </div>
  );
}


06. エラー

データ名 説明
state ステータスコード 405
statusText ステータスコードのエラーメッセージ Method Not Allowed
data 詳細なエラー Error: *****