4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Remixを触ってみて感じた良いところ&難しいところ

Last updated at Posted at 2024-11-30

結論

Remixはいいぞ!!!
サーバー側コードとクライアント側コードの切り分けは脳内で行わなければならなりませんが、裏を返せば「真の意味でのフルスタックフレームワーク」だといえます。

作ったもの

じゃんけんのタイマンが楽しめます

動機

  • DiscordでVCしていたらじゃんけんする話になったが、音声だけだと難しかった
  • 勝敗にを記録し、公式性を持たせたい

2日で作って3日で飽きられました

学んだこと

  • ルーティング
  • パスパラ取得
  • サーバー側関数
  • useFetcher

↑多分これだけ抑えれば動くものは作れます

やりたかったけど手が回ってない(そのうちやるかも?)

  • クエリパラメータ(多分簡単)
  • ロード状態監視・楽観的更新
  • 無限スクロール系(どちらかというとdbクライアントを使いこなす話になる?)

ちょっと詰まったこと

  • クライアント側環境変数の取り回し(firebaseConfigの渡し方)
  • useEffect内でのuseFetcher使用で無限リロード問題

Nextと比べてどうなの?

書きやすいです!
仮想ルーターとしてしかReact Routerを使ったことない人は是非試してほしいです!

SSGするならNext
それ以外はRemix
バックエンドがいらなければviteの使い分けがいいです。

Remix混乱したポイント

Outletに何が挿入されるか問題

Outletは子ルートのコンポーネントをレンダリングしますが、Outletとだけ書かれるので宣言的ではないと感じます。
NextでいうLayout?でもLayoutもroot.tsxにあるので使う風習はありそうです。

import { Outlet } from "react-router";

export default function SomeParent() {
 // ファイル名を確認しないと何がOutletに挿入されるかわからない
  return (
    <div>
      <h1>Parent Content</h1>
      <Outlet />
    </div>
  );
}

同じファイルなのにクライアント側コードとサーバー側コードがある

始めたては違和感がありますが、クライアント側で作成した変数をサーバー側で参照するとコンソールにエラーが出るので助かります。

↓この基本形で大抵のことはできます。

/app/routes/foo.$fooId.tsx

import { LoaderFunction } from "react-router";
import { useLoaderData } from "react-router";
import { useFetcher } from "react-router";


export const loader: LoaderFunction = async ({ request, params }) => {
  // サーバー側
  // GETの処理 外部からFetchされてもリロードされない
  const fooId = params.fooId as string; // foo.$fooId.tsxなのでパスパラメータがある
  return json({});
};


export const action: ActionFunction = async ({ request, params }) => {
  // サーバー側
  // GET以外のメソッドを処理。リソース変更が考えられるのでFetchするとリロードされる(mutationが簡単)
  return json({})
};

export default function Home() {
  // クライアント側
  const loaderData = useLoaderData();
  const fetcher = useFetcher(); // 好きなパスにリクエストするときに使う。


  return (
    <div>
    </div>
  );
}

僕は

  • ファイル数削減
  • import文削減
  • 取得、利用までの見通しが良い

の3点で今は気に入っています。

ありそうな質問

インフラは何がいい?

ほぼすべてのJavaScriptランタイムで動くので場所は選ばないです。
cloudflareは公式対応あって評判もよさそうです。
SSRをする都合上、サーバーへの負荷が高めです。スケーリングは前提にしましょう。

今回はfirebaaseを使用した都合でGCP Cloud Runにしました。

SSRは時代遅れ?そんなぁ...

詰まったことの解決法

環境変数の渡し方

バックエンドから渡します。
root.tsx内にもloaderを設置できます。

/app/root.tsx

+ export const loader: LoaderFunction = async () => {
+  return json(JSON.parse(process.env.FIREBASE_CONFIG ?? ""));
+ };

export function Layout({ children }: { children: React.ReactNode }) {
  const config = useLoaderData();
  return (
    <html lang="ja">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}


useEffect内でのuseFetcher使用で無限リロード問題

ニッチな情報かもしれません。
useFetcherとuseEffectは食い合わせが悪いです。
例えば本アプリではfirebaseドキュメントの監視を行い、変更があった場合にプレイヤーの出した手をfetchする処理があります。
監視処理の開始はuseEffectで行い、ドキュメント変更時のコールバック関数内でfetchを使用する方法が基本かと思われます。
ここで、依存配列にfetcher自体を含めるべきであるという警告が出るのですが、fetcherの状態がfetchによって書き換わるため無限に再レンダリングされます。
やむを得ずeslintを無効にしました。

参考


  // subscribe room, host user, guest user
  useEffect(() => {
    const db = getFirestore();
    const unsub = onSnapshot(doc(db, "rooms", roomId), async (rd) => {
    // 変更後のデータrdを処理する。省略
    ...
    // お互いの手を取得する(この呼び出し自体がfetcherの状態を変えるので、依存配列にfetcherを入れてはならない)
      hostHandFetcher.load(
        `/rooms/${roomId}/users/${room.hostUser}/hands/${room.hostUserRound}`
      );
      guestHandFetcher.load(
        `/rooms/${roomId}/users/${room.guestUser}/hands/${room.guestUserRound}`
      );
    });
    return () => {
      unsub();
    };

    // fetcherを依存配列に入れない
    // https://github.com/remix-run/remix/discussions/3657
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [roomId]);

↑pathはFireStoreの構造と一致させることで混乱なく設計を進められました。

/
├── room
│   ├── :roomId
│       └── users
│           ├── :userId
│               └── hands
│                   └── :handsId
├── users

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?