言語を切り替える
テーマを切り替える

React Compiler + shadcn/ui:自動最適化時代のフロントエンド開発

午前3時、React DevToolsを見つめていました。shadcnのData Tableコンポーネントがスクロール時に毎回再レンダリングされているのに、propsは全く変わっていません。手書きのuseMemoは20個以上ありましたが、まだ境界ケースを見落としていました。その時、こう思いました:自動で最適化を処理するツールがあればいいのに。

2ヶ月後、React Compiler v1.0が正式にリリースされました。useMemoを追加するかどうか悩む必要も、useCallbackを忘れる心配もありません。コンパイラがビルド時にすべてを処理します。

でも、これはshadcn/uiプロジェクトにとって何を意味するのか?Compilerを有効にした後、手書きのmemoizationは残すべきか?shadcnコンポーネントに互換性問題はないか?この記事では、数ヶ月の実践経験を紹介します。


TL;DR


一、React Compilerとは?

正直に言うと、React Compilerは「革命的」なものではありません——手書きでやるべきパフォーマンス最適化を自動化するツールです。

以前Reactを書く時、パフォーマンス問題に遭遇するたび、手動でuseMemo、useCallback、React.memoを追加する必要がありました。一つ見落とすと、ページ全体が遅くなる可能性があります。そして、このmemoizationのロジックはかなり複雑——依存関係を分析し、境界ケースを判断し、過度最適化しているかどうかを考える必要があります。

React Compilerのアプローチは:最適化ロジックは規則的なので、コンパイラがビルド時に分析し、適切なmemoizationを自動挿入します。

例えば、以前Data Tableを書く時はこうでした:

// 手動最適化版(面倒)
const columns = useMemo(() => [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
  // ... 更多列定义
], []); // 依存配列を自分で維持

const handleRowClick = useCallback((row) => {
  console.log('Clicked:', row);
}, []);

Compilerを有効にすると、このコードは直接削除できます:

// Compiler自動最適化版(簡潔)
const columns = [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
];

const handleRowClick = (row) => {
  console.log('Clicked:', row);
};

コンパイラはビルド時にこれらの関数の依存関係を分析し、memoizeするかどうかを自動判断します。「useMemoを追加するか」悩む必要がなくなりました。

公式の説明は:「build-time performance optimization」。簡単に言えば、コンパイラがReact.memoの作業をしてくれます。


二、React Compilerを有効にする:三つの方法

Next.js 16を使っているなら、Compilerは既に組み込まれています。他のビルドツールは追加設定が必要です。

方法1: Next.js 16(最も簡単)

Next.js 16はReact Compilerをデフォルトで統合しています。next.config.jsに一行追加:

// next.config.js
const nextConfig = {
  experimental: {
    reactCompiler: true, // Compilerを有効化
  },
};

export default nextConfig;

プロジェクト内のすべてのReactコンポーネントが自動最適化されます。コード変更不要です。

方法2: Vite + React Compiler Plugin

ViteプロジェクトはBabelプラグインをインストール:

npm install --save-dev babel-plugin-react-compiler

vite.config.tsで設定:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ['babel-plugin-react-compiler', {
            // オプション:コンパイルモード指定
            // 'mode': 'optimize'
          }],
        ],
      },
    }),
  ],
});

Viteがビルド時に自動的にCompilerを適用します。

方法3: 独立Babel設定(他ツール用)

webpack、Rollup、他のビルドツールを使っている場合、Babel設定に直接プラグインを追加:

// .babelrc 或 babel.config.json
{
  "plugins": [
    ["babel-plugin-react-compiler"]
  ]
}

Babelを使うすべてのビルドツールがCompilerをサポートできます。


三、shadcn/ui + React Compiler:実践経験

実際の効果について話しましょう。Compilerを使って40以上のコンポーネントを持つshadcn/ui管理画面プロジェクトを改造しました。全体体験は良好ですが、いくつかの注意点があります。

シナリオ1: Dialogコンポーネントの再レンダリング

shadcnのDialogコンポーネントは典型的な純粋コンポーネント——propsが変わらない場合、レンダリング結果も変わらない。しかし、以前手動最適化する時、DialogのonOpenChangeにuseCallbackを追加することをよく忘れました。

Compilerを有効にすると、この問題は自動的に解決しました。CompilerはonOpenChangeが安定した関数(外部依存なし)を識別し、自動的にmemoizeします。

テストしてみると、Dialogを開く時、親コンポーネントは再レンダリングしなくなりました。以前はDialogを開くたび、親コンポーネントが再レンダリング(onOpenChangeが毎回新関数だったため)。

シナリオ2: Form + Zod校验

shadcnのFormコンポーネントはReact Hook Form + Zodを使用。以前校验规则を書く時はこうでした:

// 手動最適化版
const formSchema = useMemo(() => z.object({
  username: z.string().min(2, '少なくとも2文字'),
  email: z.string().email('メール形式不正'),
}), []);

const onSubmit = useCallback((values) => {
  console.log(values);
}, []);

今、これらのuseMemo/useCallbackは削除され、直接書く:

// Compiler自動最適化版
const formSchema = z.object({
  username: z.string().min(2, '少なくとも2文字'),
  email: z.string().email('メール形式不正'),
});

const onSubmit = (values) => {
  console.log(values);
};

Compilerは自動的にこれらの関数が安定かどうかを判断。Zod schemaが毎回同じオブジェクトを返す(外部変数依存なし)場合、Compilerは自動的にmemoizeします。

シナリオ3: Data Tableレンダリング

shadcnのData TableはTanStack Tableベース。このシナリオは最もパフォーマンス問題が出やすい——列定義、ソート関数、フィルタリングロジック、どこでも手動最適化が必要かもしれません。

Compilerを有効にすると、列定義関数、イベントハンドラーは自動最適化されます。レンダリング回数をテスト:

15回/秒
レンダリング回数(正常)
来源: Compiler vs 手動最適化テスト
  • 手動最適化版:スクロール時、tableコンポーネントは15回/秒レンダリング(正常)
  • 未最適化版:スクロール時、tableコンポーネントは45回/秒レンダリング(パフォーマンス悪い)
  • Compiler自動最適化版:手動最適化と同じ、15回/秒

効果は基本的に同じ。そして30行以上の手動最適化コードを削除、可読性が大幅に向上。

Bundleサイズ影響

多くの人はCompilerがbundleサイズを増加するか心配します。実際テストでは、影響は最小——Compilerの最適化ロジックはビルド時に挿入されるため、追加のruntimeコードが増えません。

比較:

  • Compiler未使用:bundle 142KB
  • Compiler使用:bundle 144KB

2KB増加、主にコンパイラが挿入したmemoizationロジック。しかし、手書きのuseMemo/useCallbackを削除(これらは既にbundleにあった)を得て、全体影響は最小。


四、移行注意:避けるべき落とし穴

Compilerは完璧ではありません、移行時はいくつかの落とし穴に注意が必要です。

1. 命名規則が重要

Compilerは変数命名に依存して依存関係を推測します。コードがこう書かれている場合:

// ❌ Compilerが正確に分析できない可能性
function MyComponent(props) {
  return <div>{props.data.name}</div>;
}

Compilerはprops.dataの依存関係を正確に識別できない可能性があります。建议这样改:

// ✅ 明確な命名
function MyComponent({ data }) {
  return <div>{data.name}</div>;
}

こうするとCompilerがdataの依存関係をより正確に判断できます。

実は、この規則はReact公式ドキュメントでも言及——propsをデストラクトするとコードがより明確になり、Compiler分析にも有利です。

2. ESLintルールが変わる

プロジェクトにreact-hooks/exhaustive-depsルールがある場合、Compilerを有効にすると、このルールのレポートが変わります。

以前useMemoの依存を見落とした場合、ESLintがエラーを報告。今、Compilerが自動的に依存を処理するため、このルールは重要度が下がります。

ESLint設定を調整し、exhaustive-depsルールを”warn”に降格または直接オフにすることを建议。Compilerが既に依存を処理しているため、このルールは「ノイズ」になります。

// .eslintrc
{
  "rules": {
    "react-hooks/exhaustive-deps": "off" // Compilerが依存を処理済み
  }
}

3. 第三方ライブラリ互換性

一部の第三方ライブラリはCompilerと互換性がない可能性があります。特に内部に複雑な副作用ロジックがあるライブラリ。

Compilerを有効にした後、ビルドエラーまたはruntime問題が発生する場合、こう排查できます:

  1. まずエラー情報をチェック——通常あるコンポーネントの命名またはロジックがCompiler規格に合わない
  2. そのコンポーネントでCompilerを無効化('use no memo'コメント追加)
  3. 逐步排查し、互換性がないコンポーネントを見つける

第三方ドラッグライブラリが互換性がない場合に遭遇しました。解決方法はそのドラッグコンポーネントでCompilerを無効化:

'use no memo'; // Compilerに:このコンポーネントを最適化しない

function DraggableList() {
  // ドラッグロジック...
}

こうするとCompilerはこのコンポーネントをスキップし、自動最適化しなくなります。

4. Compilerを無効化する場合?

大部分のコンポーネントはCompilerを有効にしても問題ありません。しかし、いくつかのシナリオは無効化を建议:

  • 複雑な副作用ロジック:タイマー、アニメーション、DOM直接操作など、Compilerが正確に分析できない可能性
  • 第三方ライブラリ互換性なし:一部ライブラリは内部ロジックが複雑、Compilerが誤判断する可能性
  • パフォーマンスが逆に悪化:一部極端なケースで、Compilerのmemoizationが過度になり、逆にメモリ使用量が増加(この状況は稀)

無効化方法は'use no memo'コメントを追加し、Compilerにそのコンポーネントをスキップさせます。


五、手動から自動へ:私の移行ログ

実際の移行プロセスについて話しましょう。プロジェクトは40以上のshadcn/uiコンポーネントを持つ管理システムです。

移行前のコード

約50以上の手書きuseMemo/useCallback。Data Table部分が最も複雑——列定義、ソート、フィルタリング、どこでも手動最適化。

コードはかなり面倒に見えました:

// 移行前のData Table(手動最適化)
const columns = useMemo(() => [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
  // ...更多列
], []);

const sorting = useMemo(() => [{ id: 'name', desc: true }], []);

const handleSortingChange = useCallback((updater) => {
  setSorting(updater);
}, []);

移行後のコード

Compilerを有効にすると、すべての手動最適化を削除:

// 移行後のData Table(Compiler自動最適化)
const columns = [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
];

const sorting = [{ id: 'name', desc: true }];

const handleSortingChange = (updater) => {
  setSorting(updater);
};

コードが簡潔になり、可読性も大幅に向上。以前コードを読む時、各useMemoの依存が正しいか分析する必要がありました;今この問題を心配する必要がありません。

パフォーマンス比較

Lighthouseスコアをテスト:

  • 移行前:Performance 82、LCP 1.8s
  • 移行後:Performance 85、LCP 1.6s

少し良くなりました。主に不要な手動最適化を削除(一部useMemoは実際冗長)、Compilerは本当に必要な場所にのみmemoizationを挿入。

実際のレンダリング時間もテスト:

  • 手動最適化版:Data Tableスクロール時、平均レンダリング時間12ms
  • Compiler版:平均レンダリング時間10ms

ほぼ同じ、少し良い。Compilerの最適化ロジックが合理的であることを示しています。

チームフィードバック

移行完了後、いくつかの同僚の感想を聞きました:

  • 「memoするかどうか悩む必要がなくなって、かなり安心」
  • 「useMemoを削除した後、コードがかなり見やすくなった」
  • 「互換性がないコンポーネントがあったけど、‘use no memo’で解決、大丈夫」

全体フィードバックはポジティブ。皆が精神的負担が大幅に減少——毎回コードを書く時に「この関数をmemoizeするか」考えなくて済みます。


総括

React CompilerはReactエコシステムの重要な更新です。shadcn/uiプロジェクトにとって、Compilerを有効にするメリットは明確:

  1. 自動パフォーマンス最適化:手書きuseMemo/useCallback不要、Compilerがビルド時に処理
  2. コード簡潔化:手動最適化コードを削除、可読性向上
  3. 精神負担減少:毎回「memoizeするか」悩む必要なし

移行時はいくつかのポイントに注意:

  • 命名規則:propsをデストラクトし、props.dataのような書き方を避ける
  • ESLint設定:exhaustive-depsルールを調整
  • 第三方ライブラリ互換性:問題があれば'use no memo'で無効化

Next.js 16プロジェクトでまず試すことを建议——すぐ使える、設定が最も簡単。他のビルドツールはViteの設定方法を参考に、逐步移行。

正直に言うと、Compilerを使った後、Reactを書く感覚が以前と違くなりました——「普通」のJavaScriptを書くような感じ、パフォーマンス最適化の細部を常に考える必要がありません。この感覚はかなり良いです。


FAQ

FAQ

React Compilerはbundleサイズを増加しますか?
大幅な増加はありません。実際テストでは、Compiler有効化後bundleは約2KB増加のみ、主にコンパイラが挿入したmemoizationロジック。しかし、手書きuseMemo/useCallbackの削除を得て、全体影響は最小です。
shadcn/uiコンポーネントはReact Compilerと互換性がありますか?
大部分のshadcn/uiコンポーネントは純粋コンポーネントで、Compilerが依存関係をよく識別・最適化できます。しかし、一部内部ロジックが複雑なコンポーネントは'use no memo'コメントでCompilerを無効化する必要があります。逐步移行を建议し、問題があれば無効化してください。
Compiler有効化後、手書きuseMemo/useCallbackは削除すべきですか?
削除を建议します。Compilerは必要な場所に自動的にmemoizationを挿入するため、手書きuseMemo/useCallbackは冗長コードになる可能性があります。移行後コードが簡潔になり、パフォーマンスは基本的に同じまたはより良いです。
どのシナリオでCompilerを無効化すべきですか?
複雑な副作用ロジック(タイマー、アニメーション、DOM操作)、第三方ライブラリ互換性なし、パフォーマンスが逆に悪化する極端なケース。無効化方法:コンポーネント先頭に'use no memo'コメントを追加。
Compilerにどのような命名要件がありますか?
propsをデストラクトし、直接props.dataを使うことを避けることを建议します。例えばconst { data } = propsまたはfunction MyComponent({ data })を使用すると、Compilerが依存関係をより正確に分析できます。
Next.js 16とViteプロジェクトでCompilerを有効にする方法は?
Next.js 16:next.config.jsにexperimental: { reactCompiler: true }を追加。Vite:babel-plugin-react-compilerをインストールし、vite.config.tsのreact pluginのbabel.pluginsで設定。

6 min read · 公開日: 2026年3月31日 · 更新日: 2026年3月31日

コメント

GitHubアカウントでログインしてコメントできます

関連記事