[React] モノレポ環境でのReact Compiler導入記

2026-03-04 hit count image

6つのアプリと共有パッケージで構成されたモノレポにReact Compilerを一括導入した過程と遭遇した問題、panicThresholdの選択、eslint-disable戦略、ref.currentの誤検知対応、そして問題発生時の"use no memo"ディレクティブの活用方法を共有します。

react

概要

以前のブログ記事で、Reactのレンダリング最適化とuseMemouseCallbackReact.memoを活用したメモ化について解説しました。まだ読んでいない方は、以下のリンクを参照してください。

React 19へのアップグレードが完了した後、自然と次のステップとしてReact Compilerの導入を検討することになりました。React 19のマイグレーションについては、以下のリンクを参照してください。

React Compilerはコンポーネントとフックを自動的にメモ化し、手動でuseMemouseCallbackReact.memoを記述しなくても不要なリレンダリングを防止してくれます。今回のブログ記事では、6つのアプリと共有パッケージで構成されたモノレポにReact Compilerを一括導入した過程と、その中で遭遇した問題を共有します。

環境

導入対象プロジェクトの環境は以下のとおりです。

  • React 19、Vite 5、TypeScript
  • Yarn Workspacesベースのモノレポ(6つのアプリ + 共有ライブラリ)
  • ESLint 8(レガシー設定形式)
  • 全て関数コンポーネント(Classコンポーネントなし)

導入プロセス

パッケージのインストール

まず、必要なパッケージをインストールします。

# babel-plugin-react-compilerのインストール
yarn add -D [email protected]

# eslint-plugin-react-hooksのアップグレード(v6にコンパイラ検証ルールが統合)
# eslint-plugin-react-compilerのインストール
yarn add -D eslint-plugin-react-hooks@^6.1.0 [email protected]

注意すべき点は、eslint-plugin-react-hooks v6のrecommended-latestプリセットはESLint 9のflat config形式でのみ動作するということです。ESLint 8のレガシー形式を使用している場合は、既存のrecommendedを維持し、eslint-plugin-react-compilerを別途インストールする必要があります。

ESLintの設定

ESLintの共有設定ファイルにreact-compilerプラグインとルールを以下のように追加します。

// ESLint共有設定
module.exports = {
  extends: ['./index.js', 'plugin:react-hooks/recommended'],
  plugins: ['react-refresh', 'react-compiler'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
    'react-compiler/react-compiler': 'error',
  },
};

react-compiler/react-compilerルールをerrorに設定すると、Reactのルールに違反するコードをビルド前に検出できます。

ReactプロジェクトでESLintを設定する方法についてさらに詳しく知りたい方は、以下のリンクを参照してください。

Viteの設定

各アプリのvite.config.tsに以下のようにBabelプラグインを追加します。

import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [['babel-plugin-react-compiler', { panicThreshold: 'NONE' }]],
      },
    }),
  ],
});

モノレポで各アプリが共有ライブラリのソースをインポートしている場合、各アプリのビルド時に自動的にコンパイルされるため、共有パッケージに別途設定は不要です。設定はアプリレベルのvite.config.tsにのみ追加すれば大丈夫です。

ViteベースのReactプロジェクトの始め方については、以下のリンクを参照してください。

遭遇した問題

panicThresholdの選択

最初はpanicThreshold: 'CRITICAL_ERRORS'に設定しました。このオプションは最適化できないコンポーネントをスキップしつつ、深刻なエラーのみビルドを失敗させます。

しかし、既存コードにあったeslint-disable react-hooks/exhaustive-depsコメントがコンパイラによってcritical errorに分類され、ビルドが失敗しました。このパターンがプロジェクト全体で313箇所も存在したため、panicThreshold: 'NONE'に変更して、該当コンポーネントは自動的に最適化をスキップするようにしました。

panicThresholdの各オプションは以下のとおりです。

オプション動作
NONE最適化不可の場合、静かにスキップ
CRITICAL_ERRORS深刻な違反のみビルド失敗
ALL_ERRORSすべての違反でビルド失敗

段階的な導入のためには、NONEから始めて、安定化後に段階的に引き上げることを推奨します。

eslint-disableコメント戦略

react-compiler/react-compilerルールをerrorに設定すると、既存のhooksルールに違反していた313箇所でlintエラーが発生します。これに対処するために、2つの方法を検討しました。

ファイル先頭に一括追加(不採用)

/* eslint-disable react-compiler/react-compiler */

ファイル全体のコンパイラ検証が無効化され、同じファイル内の他のコンポーネントまで検証が漏れてしまいます。

問題発生箇所にインラインで追加(採用)

// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps

正確な箇所にのみ適用されるため、同じファイルの他のコードは正常に検証されます。lintを実行してエラーが発生する正確なファイルと行番号を抽出し、スクリプトで一括挿入しました。

ref.currentの変異の誤検知

イベントハンドラ内でref.currentを通じてDOMを操作することは、Reactのルールに違反しません。しかし、コンパイラは「関数の戻り値を変異してはいけない」という警告を発生させる場合があります。

const handleLoadedMetadata = () => {
  const video = videoRef.current;
  if (!video || !timelineRef.current) return;

  video.currentTime = 0; // コンパイラ警告発生
  timelineRef.current.setCurrentTime(0); // コンパイラ警告発生
};

このようなfalse positiveの場合も、eslint-disable-next-lineで抑制するのが適切です。

リスクとテストガイド

レベル別リスク

高 — レンダリング動作の変更

コンパイラが自動的にメモ化を挿入することで、既存の毎レンダリングで実行されていたコードがスキップされる可能性があります。

  • useEffectの外でサイドエフェクトを実行するコード
  • レンダリング中に毎回新しいオブジェクト/配列を生成することに依存するロジック
  • レンダリング中にref.currentを読み書きするパターン

Reactのレンダリング動作については、以下のブログを参照してください。

高 — カスタムReconcilerを使用するライブラリ

react-konvaのようにReactの内部reconcilerをカスタマイズして使用するライブラリは、コンパイラのメモ化と衝突する可能性が高いです。Canvasベースのレンダリング(アノテーション、バウンディングボックスなど)を重点的にテストする必要があります。

中 — チャート/キーボード関連ライブラリ

rechartsreact-hotkeys-hookなどは内部的に独自のstate管理やグローバルイベントリスナーを使用しています。チャートレンダリングとキーボードショートカットの動作を確認する必要があります。

低 — 純粋レンダリング/分析ライブラリ

qrcode.reactreact-ga4などは純粋なレンダリングコンポーネントかサイドエフェクトのみを実行するため、リスクは低いですが、正常に動作するか確認することをお勧めします。

重点テスト項目

項目確認ポイント
ページ初期ロードuseEffect初期化ロジックの正常実行(データfetch、状態セット)
フォーム入力/バリデーション入力値の即時反映、エラーメッセージ表示タイミング
ダイアログの開閉状態の初期化、データのリロード
テーブルソート/ページネーションソート順序、ページ遷移後のデータ
無限スクロールIntersectionObserverベースのスクロールロード
ルーティング遷移状態のクリーンアップと新ページの初期化

問題発生時の対応:“use no memo”ディレクティブ

テストやQA中に特定のコンポーネントで異常動作が見つかった場合、"use no memo"ディレクティブでそのコンポーネントのみコンパイラ最適化から除外できます。

const ProblematicComponent = () => {
  'use no memo';

  return <div>...</div>;
};

ESLint disableとの違い

この2つは動作レベルが異なります。

"use no memo"eslint-disable react-compiler
影響範囲コンパイラ自体が該当関数をスキップESLint警告のみ非表示
ビルド時最適化コードを生成しないpanicThresholdに依存
用途ランタイムで問題発生時lintエラーの抑制

対応フロー

テストやQA中に問題が見つかった場合、以下のように対応できます。

  1. テストまたはQAで異常動作を発見
  2. 該当コンポーネントに"use no memo"を追加
  3. 問題解決を確認 → 解決すれば維持
  4. 根本原因を特定しコードを修正 → "use no memo"を削除

ブラウザのReact DevToolsでMemo ✨表示があるコンポーネントが、コンパイラによって最適化されたものです。

React DevTools - React Compilerによってmemo表示されたコンポーネント

まとめ

React Compiler導入のポイントは段階的なアプローチです。

  • panicThreshold: 'NONE'から始めて、最適化できないコンポーネントは自動的にスキップさせます。
  • ESLintルールはerrorに設定しつつ、既存の違反箇所にはインラインコメントで抑制します。
  • 問題が発生したら"use no memo"で個別コンポーネントをopt-outします。
  • 安定化後に手動のuseMemo/useCallback/React.memoを削除し、panicThresholdを段階的に引き上げることができます。

以前のブログ記事で取り上げた手動メモ化の複雑さとミスの可能性を考えると、React Compilerの自動最適化は大きな改善です。ただし、既存コードベースのルール違反が多いほど導入時に遭遇する問題も多くなるため、段階的にアプローチすることが重要です。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS