白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

RemixのSingle Fetchがいい感じ

こんばんは。
急に寒くなりすぎて体がついていけていない気がします。

Feature Flag

RemixにはFeature Flagという近い将来採用される機能を明示的にONにする機能があります。
これは設定でONにするだけで新しい機能が使えるとても便利な機能です。

remix.run

この中にV3で採用予定のSingle Fetchがあるので、今日はこれの良さを。

remix.run

導入方法

上に張ったリンクの通りなのですが、基本的にはvite.config.tsに設定を1行追加するのと、インタフェース拡張をするだけです。

export default defineConfig({
  plugins: [
    remix({
      future: {
        // ...
        v3_singleFetch: true,  // これ
      },
    }),
    // ...
  ],
});

インタフェース拡張はtsconfig.jsonのincludeしてファイルならどこでもいいようですが、vite.config.tsに一緒に書いておくのが簡単そうですかね。
「@remix-run/server-runtime」のところは「@remix-run/node」とか「@remix-run/cloudflare」とか環境に応じてで。

declare module "@remix-run/server-runtime" {
  // or cloudflare, deno, etc.
  interface Future {
    v3_singleFetch: true;
  }
}

そのほか既存のプロジェクトで有効にする際は気を付けることがいくつかあるようですが、そのあたりは公式ドキュメントにお任せします。
ちなみに先ほど「npx create-remix@latest」のコマンドを叩いたら、最初から有効になっていました。いつのまに?

何ができるようになるのか

名前の通りなのですが、SingleにFetchができるようになります。
つまりどういうことかというと、Remixではリクエスト時にパスに対応するloaderがすべて別々に呼び出されてページを描画していましたが、これがまとめて呼ばれるようになりました。
(URLを直接叩くときはSSRなのでLinkタグを使って遷移するときなどですね)

まずはSingle FetchをOFFにして、こんな感じでOutletを使ったページを作ってみます。

// ---------- a.tsx ----------
export function loader() {
    console.log("a.tsx");
    return json({
        message: "Aだよ"
    });
}

export default function Index() {
    const { message } = useLoaderData<typeof loader>();
    return (
<>
    <div className="mt-4">
        <h2>Page A</h2>
        <p>{message}</p>
    </div>
    <Outlet />
</>
    );
}

// ---------- a.b.tsx ----------
export function loader() {
    console.log("a.b.tsx");
    return json({
        message: "Bだよ"
    });
}

export default function Index() {
    const { message } = useLoaderData<typeof loader>(); 
    return (
<>
    <div className="mt-4">
        <h2>Page A.B</h2>
        <p>{message}</p>
    </div>
    <Outlet />
</>
    );
}

// ---------- a.b.c.tsx ----------
export function loader() {
    console.log("a.b.c.tsx");
    return json({
        message: "Cだよ"
    });
}

export default function Index() {
    const { message } = useLoaderData<typeof loader>(); 
    return (
<div className="mt-4">
    <h3>Page A.B.C</h3>
    <p>{message}</p>
</div>
    );
}

// ---------- _index.tsx ----------
export default function Index() {
  return (
    <></>
  );
}

// ---------- root.tsx ----------
export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body className="center mx-auto my-8 max-w-2xl">
        <header>
          <nav>
            <Link className="text-white bg-blue-500 p-2 mx-2 rounded-lg" to="/">Top</Link>
            <Link className="text-white bg-blue-500 p-2 mx-2 rounded-lg" to="/a/">Page A</Link>
            <Link className="text-white bg-blue-500 p-2 mx-2 rounded-lg" to="/a/b/">Page B</Link>
            <Link className="text-white bg-blue-500 p-2 mx-2 rounded-lg" to="/a/b/c/">Page C</Link>
          </nav>
        </header>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

トップページから/a/に飛ぶとaのデータだけがfetchされますが、

同様にトップページから/a/b/c/に飛ぶとa,b,cのそれぞれのデータがfetchされているのがわかります。

ここでSingle FetchをONにすると、こんな感じで1つのリクエストにまとまっています。
URLの形式も「c/?_data=routes%2Fa.b.c」から「c.data」に変わっていますね。
この時点ではFeature Flagの設定のみでコードは全く変えていません。

何がうれしいかというとパフォーマンス向上とか、キャッシュが効きやすくなるとかそんな感じですね。

型が効くぞー

で、どちらかというと付随的な更新なのですが、個人的にはこちらのほうが嬉しみが大きい変更にストリーミング方式の変更があります。

もともとloaderから渡されるオブジェクトはJSONシリアライズしていました。
なので、useLoaderDataで渡ってくるJsonifyObjectという謎の型を詰め替えたり変換したりする必要があって面倒でした。

const data: JsonifyObject<{
    message: string;
}>

そしてこれをそのままの型で使えるようにしたremix-typedjsonというライブラリもなかなかに人気で、私もお世話になっていました。
github.com

一方でSingle Fetchでは生のオブジェクトが直接ストリーミングされるようになりました。(nakedってなんて訳すの?カタカナでそのままネイキットって翻訳されちゃう)
loaderではjson化する必要はなく、そのままオブジェクトを返すことになります。これは非常に直感的で使いやすいです。
(ちなみにjsonメソッドをそのまま使うこと自体は可能ですが、もう非推奨みたいです)

export function loader() {
    return {
        message: "Aだよ"
    };
}

Single Fetchに乗り換えよう

デフォルトでFeature FlagがONになっており、jsonメソッドも非推奨になったのでSingle Fetchへの乗り換えはそろそろ必須になってきそうです。
V3(React Router v7?)ではデフォルトなので今のうちに準備をしておきたいですね。