React Router のプロジェクトに React Compiler を導入する
はじめに
React Compiler が stable になってまあまあの月日が経ちました。
私は自動でメモ化してくれる魔法のような React Compiler をずっと導入したいと考えていました。そして stable になったと知ったとき、すぐ既存の React Router v7 のプロジェクトに React Compiler を導入しようと試みました。
しかしその時は何かが上手くいかなくて(気のせいだったかもしれません…メモしておけばよかった)、それ以来ずっとまだ導入できないものだと思いこんでいました。
実際、現時点においても Web で調べてみると同様のことができたというページは見つからず、さらに AI に聞いてみても(調べさせても)「できない可能性があります」といったような回答がきて、ああそういうことなんだなと納得してしまっていました。
ところが最近ちゃんと自分で試してみないとダメだよなと思い立ち、試しにやってみたところ、案外すんなり React Compiler を導入できてしまいました…。ちゃんと自分の手で動かして確認しないとダメですね。もしかしたら同じようなことを考えている方がいるかもしれないので(いるわけない)、恥ずかしながら共有させていただきます。
まず結論から
React の公式ドキュメントの通りにインストールすれば全く問題ありません。
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import babel from "vite-plugin-babel";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
tailwindcss(),
reactRouter(),
tsconfigPaths(),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ["@babel/preset-typescript"],
plugins: [
["babel-plugin-react-compiler"],
],
},
})
],
});
本当にこれだけでした…。最初何をもって導入できないと判断したのか分かりません。夢だったのかな。
本当に適用されているか検証する
一応、ちゃんと適用されているかどうかを検証していきます。
まず React Router をインストールしたあと、welcome.tsxに以下のようなコードを書いてみます。
import { useState } from "react";
type Pokemon = {
id: number;
name: string;
canEvolve: boolean;
};
const pokemons: Pokemon[] = [
{ id: 1, name: 'フシギダネ', canEvolve: true },
{ id: 2, name: 'フシギソウ', canEvolve: true },
{ id: 3, name: 'フシギバナ', canEvolve: false },
{ id: 4, name: 'ヒトカゲ', canEvolve: true },
{ id: 5, name: 'リザード', canEvolve: true },
{ id: 6, name: 'リザードン', canEvolve: false },
{ id: 25, name: 'ピカチュウ', canEvolve: true },
{ id: 26, name: 'ライチュウ', canEvolve: false },
];
function PokemonSummaries({ pokemons }: { pokemons: Pokemon[] }) {
const evolvablePokemons = pokemons.filter(p => p.canEvolve);
return <div>進化可能: {evolvablePokemons.length}</div>;
}
export function Welcome() {
const [count, setCount] = useState(0);
return (
<main
className="flex items-center justify-center pt-16 pb-4"
>
<div className="flex-1 flex flex-col items-center gap-16 min-h-0">
<div className="max-w-[300px] w-full space-y-6 px-4">
<nav className="rounded-3xl border border-gray-200 p-6 dark:border-gray-700 space-y-4">
<p className="leading-6 text-gray-700 dark:text-gray-200 text-center">
React Router カウンター
</p>
<PokemonSummaries pokemons={pokemons} />
<div className="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4 text-center">
<p className="text-lg font-semibold">カウント: {count}</p>
<div className="mt-3 flex justify-center gap-3">
<button
onClick={() => setCount(c => c - 1)}
className="px-4 py-2 bg-gray-200 text-gray-900 rounded hover:bg-gray-300 transition-colors dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600"
>
-1
</button>
<button
onClick={() => setCount(c => c + 1)}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
+1
</button>
</div>
</div>
</nav>
</div>
</div>
</main>
);
}
シンプルなカウンター処理の中で、ポケモンの情報を表示するコンポーネントPokemonSummariesを呼び出しています。
ここで重要なのは、PokemonSummariesは memo していないことです。
つまり React Compiler を有効にしていない場合は、カウンターのボタンをクリックする度に、PokemonSummariesが再レンダリングされます。

これはそのときの React Developer Tools のスクリーンショットです。
カウントの状態を使用していないのにPokemonSummariesが再レンダリングされていることがお分かりかと思います。
次に React Compiler を有効にしてみます。そうすると…

PokemonSummariesが再レンダリングされておらず、さらに右のコード部分に Memo✨️と表示されるようになりました。
これはまさに React Compiler の自動メモ化によって最適化されている証拠と言えるかと思います。
おわりに
まあこれは極々簡単なロジックにおける検証でしたので、もっと複雑なことをしているコードだと React Compiler が有効にならないパターンがあるのかもしれませんね。
私が最初に上手く動かないと判断したのもそういった理由だったのかもしれません。
また何か分かれば記事にしようと思います。
Discussion