
はじめに
React Compilerを2024年7月末に導入し、2024年10月中旬からプロダクション利用を開始して現在(2025年5月中旬)に至ります
これまでに起きたことや感想を共有したいと思います
前提
技術スタック
- Next.jsのPages Routerを使用し、サーバー側で動く処理はほぼない
- react-hook-formを使用
- reactStrictModeはtrue
- 導入時に
eslint-plugin-react-compiler
を使った検査では違反なし - 各ライブラリのバージョンアップは随時行っている
// 2024年7月末の導入時 next: 15.0.0-rc.0 react: 19.0.0-rc-01172397-20240716 babel-plugin-react-compiler: 0.0.0-experimental-938cd9a-20240601 react-hook-form: 7.52.1
// 2024年10月中旬のプロダクション利用開始時 next: 15.0.0-rc.1 react: 19.0.0-rc-cd22717c-20241013 babel-plugin-react-compiler: 0.0.0-experimental-fa06e2c-20241016 react-hook-form: 7.53.0
// 2025年5月中旬の執筆時 next: 15.3.2 react: 19.1.0 babel-plugin-react-compiler: 19.1.0-rc.1 react-hook-form: 7.55.0
開発スタイル
- フロントエンドのコードにコミットをする主なメンバーは1~2人
- 開発者がテストも行う(QAやテスター等開発者以外にテストを行ってくれる人はいない)
対象読者
- React Compilerの導入を検討している人
結論
- プロダクションでReact Compiler起因の不具合はまだ1つも起きていない
- React Compilerは2025年5月中旬時点でRC, Nextではまだexperimentalな機能なので、アップデートのたびに挙動が変わる可能性はある
- アップデートによりアプリケーションの動作が期待通りに動かなくなったことはない
- Next.js 15.3.0まではReact Compilerが効いていたが、15.3.1, 15.3.2では恐らく効かなくなった
- 私の経験ではこれまでに意図した通りに動かなくなった箇所はrefとreact-hook-formを使っている箇所のみ
-
next dev
とnext build
で挙動が変わるケースがよくあった - 開発中に、原因がわからず意図した通りに動かなくなることはあるが、一貫して同じ箇所が動かなくなるので気づきやすい
-
useMemo
,useCallback
,React.memo
は今後原則書かないことに決められたのがよかった - Reactやreact-hook-formと向き合う時間が取れないチームは採用しない方が良い
本文
React Compilerとは?
Reactアプリを自動的にメモ化するビルドツールです
useMemo、useCallback、React.memoなどを書かなくても、よしなに最適化してくれるイメージです
詳しくは公式ドキュメントを参照してください。ボリュームは少ないので読みやすいと思います。日本語版の公式ドキュメントもありますが、英語版の方がアップデートが進んでいることがあります
プロダクションへの導入きっかけ
リリース前に開発者のみがアプリケーションを利用する期間があった
当時プロダクションのデータ構造を大幅に変更する必要があり、それまで使っていたアプリケーションはそのまま運用し続け、新たにアプリケーションを複製して開発を進めることにしました
開発開始からリリースまでの3ヶ月程は、壊しながら開発をしても問題がないため気軽に変更を反映でき、またリリース前には全ての機能をテストする想定でいたため、当時experimentalだったReact Compilerも導入しやすい状況でした
簡単に剥がせるから
導入するのも剥がすのもNext.jsの設定ファイルを1項目変えるだけでほぼノーコストなので、導入を断念したとしても無駄な時間はあまり発生しない見込みだったのも、大きな理由でした
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
},
}
export default nextConfig
リリースまでに修正したReact Compilerの問題
React Compilerを有効にしてから意図した通りに動かなくなるケースがありました
私の経験では、動かなくなったのは全てreact-hook-formかrefを使用しているコードでした
ref
refを使ったコードが一箇所だけ動かなくなりました。決まった行数を超えると「続きを読む」が出てきて、決まった行数以降を省略するコンポーネントです
決まった行数を超えたかどうかを判定するカスタムフックとコードは元々こんなイメージでした
export const useTruncateTextBlock = () => {
const [isTruncated, setIsTruncated] = useState(false)
const measuredRef = (node: HTMLDivElement | null) => {
if (!node) {
return
}
setIsTruncated(node.scrollHeight > node.offsetHeight)
}
return {
isTruncated,
measuredRef
}
}
export const ClippedText: React.FCProps> = ({ children, maxLines = 12 }) => {
const [isOpen, setIsOpen] = useState(false)
const { measuredRef, isTruncated } = useTruncateTextBlock()
return (
div>
div ref={measuredRef} css={contentStyle(isOpen, maxLines)}>
{children}
div>
{isTruncated && !isOpen && ReadMoreButton setIsOpen={setIsOpen} />}
div>
)
}
原因ははっきりしませんが、親コンポーネントでのタブの切り替えに、display: noneを使っているのが関わってそうな気がしています
今回は高さの算出にResizeObserverを使うことで解消しました
export const useTruncateTextBlock = () => {
const [isTruncated, setIsTruncated] = useState(false)
const measuredRef = (node: HTMLDivElement | null) => {
if (!node) {
return
}
const observer = new ResizeObserver(([entry]) => {
if (!entry) {
return
}
const target = entry.target as HTMLDivElement
setIsTruncated(target.scrollHeight > target.offsetHeight)
})
observer.observe(node)
return () => {
observer.disconnect()
}
}
return {
isTruncated,
measuredRef
}
}
react-hook-formで一貫して動かなかった書き方
色々と意図した通り動かないことがありましたが、まず規則性があったのがwatch
とformState
です
- watchが動かない
- useWatchを使うことで解消した
- react-hook-formの7.55.0だとwatchでも動いてそう
- formStateから正しい状態が取れない
- useFormStatusを使うことで解消した
- useFormContextの戻り値のformStateが特に動かなかった記憶
react-hook-formで動いたり動かなかったりした書き方
動いている箇所もあれば動かない箇所もあったのがvalues
やregister
でした
- useFormのvaluesが変わっても更新されないことがあった
- defaultValuesを使い、親コンポーネントのpropsのkeyに、valuesを入れて解消した
- react-hook-formの7.55.0だとvaluesも動いてそう
- registerを使っていると動かなくなる箇所があった
- controlを使うことで解消した
- registerを使って動いている箇所もたくさんある
- 同display:noneを使って描画切り替えをしているのが怪しい
next devでは動いていたのに、next buildをすると動かなくなることが何度かあった
React Compiler導入前までは、基本的にローカル開発時にはnext dev
での動作確認をするだけで、一応開発環境にデプロイした後(next build
をしている)に動作確認をして挙動が変わることはほぼありませんでした
React Compilerを有効にしてからは、next build
のときだけ動かなくなることが何度かあったので、react-hook-formに関わる変更をした際は、ローカルでもnext build
をして動作確認するようにしました
フォームの数が多くない(useFormの宣言が38箇所)アプリケーションのため、ローカルでnext build
をして動作確認をする機会は少ないですが、フォームが多いアプリケーションだと大変かなと思います
確認と修正を繰り返すときに、next build
の待ち時間が長く感じるようになったので、next dev
とnext build
のアウトプットディレクトリを別にし、next dev
で動作確認している間にnext build
を実行しておく小技を導入しました
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true,
distDir:
process.env.NODE_ENV === 'production'
? '.next'
: '.next-dev',
experimental: {
reactCompiler: true
}
}
export default nextConfig
Next.js 15.3.1, 15.3.2ではReact Compilerが効いていなさそう
Next.js 15.3.1-canary.9以降、React Compilerは効いていないのではないかと思われます
Next.js 15.3.1-canary.8まではReact Compilerが効いている様子だったため、そう判断しています
その後は修正と切り戻しが何度か繰り返されている様子です
この記事を書くにあたって以前動かなかった書き方を試したところ、全て問題なく動くようになっていたので、しばらくテンションあがっていたのですが、React Developer Toolsを使って確認したところ、Next.js 15.3.0までは自動でメモ化されていたコンポーネントが軒並みメモ化されなくなっていました
React Compiler導入後の変化
不要な再レンダリングが減った
当然ですがReact Compilerが効いている場合は再レンダリングは減っています
ただユーザーの体験としてほぼ何も感じ取れません
前述の通りNext.js 15.3.2ではReact Compilerが効いてなさそうですが、この記事を書くまで気づいていませんでした
useMemo, useCallback, React.memoは原則増やさないことにした
メモ化するかどうかはReactのドキュメントには下記のように書いてあります
「useCallbackはとにかく使え! 特にカスタムフックでは」の主張に共感しており、メモ化するかどうかの基準に複雑さは持ち込みたくないです
再レンダリングの抑制をすることで、ユーザー体験が向上する箇所はこれまでほぼなかったため(前述の通りメモ化できていないことに気づいていなかったことが答えです)、React Compiler導入後には、新たに書くコードへはuseMemo, useCallback, React.memoを増やさないことにしました
一部React的には好ましくない、メモ化をしないとuseEffectの無限ループが発生してしまう箇所があることもあり、これまで書いていたコードを直すことは積極的には行っていませんが、よりわかりやすい方針にできたのは嬉しいです
ちゃんと動いていた箇所がいつの間にか壊れていたことはまだない
アプリケーションの移行期や、新たにフォームを実装する場合は、React Compilerを有効にした場合のみ動かないケースはありますが、その後のパッケージアップデートや、フォームに軽微な機能追加、修正等を行って、再び壊れたことはまだないです
ここはなんとなくありそうで毎度不安になるのですが、意外とそうでもないのでよかったです
React Compilerの有効化をおすすめ出来ないチームやアプリケーション
Reactの方針に沿ったコードになっていない箇所が多かったり、react-hook-formと向き合う時間が取れない場合は、何かがおかしくなったときに疑う要因がさらに増えるので辞めておくのが良いと思います
react-hook-formはdefaultValuesにundefinedを使うのを避けるべき、formStateを購読する場合の論理演算子の使い方に注意、といった、一見するとJavaScriptやTypeScript的には問題ないので、好ましくないコードを書いても気づかないことが多いです
ここにさらにReact Compilerが入ってくると、思った通りに動かない要因がさらに増えるので、かなり混乱すると思います
また、既存のアプリケーションにいきなり全適用するのは結構怖いと思います
やるなら全てテストする気持ちで臨むか、Next.jsのannotationモードを使って段階的に導入するのが良いと思います
まとめ
たまたま絶好の機会があったおかげでReact Compilerを試すことができ幸運でした"use no memo"
で部分的に逃げるか、"use no memo"
だらけになりそうなら大人しく無効にすればよいというのも非常に気楽でした
また、壊れる理由がわからないことはあっても、壊れるときは一貫した挙動をしてくれる、いつの間にか壊れることも今のところないのも嬉しいポイントです
私のアプリケーションの場合はメモ化によるユーザー体験向上はほぼないと思いますが、開発者としてメモ化に関するコードは書かないと決められたことが大きな収穫でした
各種ライブラリのアップデートでどんどん使いやすくなっている様子(今は一時的に効いていないようですが)なので、これからアプリケーションを作り始める人は導入を検討してみると良いと思います
Views: 1