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を有効にすると、列定義関数、イベントハンドラーは自動最適化されます。レンダリング回数をテスト:
- 手動最適化版:スクロール時、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問題が発生する場合、こう排查できます:
- まずエラー情報をチェック——通常あるコンポーネントの命名またはロジックがCompiler規格に合わない
- そのコンポーネントでCompilerを無効化(
'use no memo'コメント追加) - 逐步排查し、互換性がないコンポーネントを見つける
第三方ドラッグライブラリが互換性がない場合に遭遇しました。解決方法はそのドラッグコンポーネントで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を有効にするメリットは明確:
- 自動パフォーマンス最適化:手書きuseMemo/useCallback不要、Compilerがビルド時に処理
- コード簡潔化:手動最適化コードを削除、可読性向上
- 精神負担減少:毎回「memoizeするか」悩む必要なし
移行時はいくつかのポイントに注意:
- 命名規則:propsをデストラクトし、
props.dataのような書き方を避ける - ESLint設定:exhaustive-depsルールを調整
- 第三方ライブラリ互換性:問題があれば
'use no memo'で無効化
Next.js 16プロジェクトでまず試すことを建议——すぐ使える、設定が最も簡単。他のビルドツールはViteの設定方法を参考に、逐步移行。
正直に言うと、Compilerを使った後、Reactを書く感覚が以前と違くなりました——「普通」のJavaScriptを書くような感じ、パフォーマンス最適化の細部を常に考える必要がありません。この感覚はかなり良いです。
FAQ
FAQ
React Compilerはbundleサイズを増加しますか?
shadcn/uiコンポーネントはReact Compilerと互換性がありますか?
Compiler有効化後、手書きuseMemo/useCallbackは削除すべきですか?
どのシナリオでCompilerを無効化すべきですか?
Compilerにどのような命名要件がありますか?
Next.js 16とViteプロジェクトでCompilerを有効にする方法は?
6 min read · 公開日: 2026年3月31日 · 更新日: 2026年3月31日
関連記事
Astro + Tailwind:アイランドコンポーネントとグローバルスタイルの競合を防ぐ設定
Astro + Tailwind:アイランドコンポーネントとグローバルスタイルの競合を防ぐ設定
Next.js App Router + shadcn/ui:サーバーコンポーネントとクライアントコンポーネントの混用ガイド
Next.js App Router + shadcn/ui:サーバーコンポーネントとクライアントコンポーネントの混用ガイド
Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト

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