このページでは、React Compiler の概要と、正常に試す方法を紹介します。
学習内容
- コンパイラの開始
- コンパイラと ESLint プラグインのインストール
- トラブルシューティング
React Compiler は、コミュニティからの早期フィードバックを得るためにオープンソース化された新しいコンパイラです。これはビルド時のみのツールであり、React アプリを自動的に最適化します。プレーンな JavaScript で動作し、React のルールを理解しているため、使用するためにコードを書き直す必要はありません。
コンパイラには、エディタでコンパイラからの分析を表示するESLint プラグインも含まれています。本日、すべての人がリンターを使用することを強くお勧めします。リンターはコンパイラがインストールされていることを必要としないため、コンパイラを試す準備ができていない場合でも使用できます。
コンパイラは現在 beta
としてリリースされており、React 17 以降のアプリとライブラリで試用できます。ベータ版をインストールするには
または、Yarn を使用している場合
まだ React 19 を使用していない場合は、詳細な手順について、以下のセクションを参照してください。
コンパイラは何をしますか?
アプリケーションを最適化するために、React Compiler はコードを自動的にメモ化します。現在、useMemo
、useCallback
、React.memo
などの API を通じてメモ化に慣れているかもしれません。これらの API を使用すると、入力が変更されていない場合、アプリケーションの特定の部分を再計算する必要がないことを React に伝えることができ、更新時の作業を減らすことができます。強力な一方で、メモ化を適用したり、誤って適用したりすることを忘れがちです。これにより、React が意味のある変更のない UI の部分をチェックしなければならなくなるため、非効率的な更新につながる可能性があります。
コンパイラは、JavaScript と React のルールに関する知識を使用して、コンポーネントとフック内の値または値のグループを自動的にメモ化します。ルールの違反を検出すると、影響を受けたコンポーネントまたはフックのみを自動的にスキップし、他のコードの安全なコンパイルを継続します。
コードベースがすでに十分にメモ化されている場合は、コンパイラを使用しても大幅なパフォーマンスの向上は見られない可能性があります。ただし、実際には、パフォーマンスの問題を引き起こす正しい依存関係をメモ化するのは手作業で正しく行うのが難しいです。
詳細
React Compiler の最初のリリースは、主に更新パフォーマンスの向上(既存のコンポーネントの再レンダリング)に重点を置いているため、次の 2 つのユースケースに焦点を当てています
- コンポーネントのカスケード再レンダリングのスキップ
<Parent />
を再レンダリングすると、<Parent />
のみが変更されたにもかかわらず、コンポーネントツリー内の多くのコンポーネントが再レンダリングされます。
- React の外部からのコストのかかる計算のスキップ
- 例えば、データが必要なコンポーネントやフックの中で
expensivelyProcessAReallyLargeArrayOfObjects()
を呼び出す場合などです。
- 例えば、データが必要なコンポーネントやフックの中で
再レンダリングの最適化
React では、UI を現在の状態 (より具体的には、props、state、context) の関数として表現できます。現在の実装では、コンポーネントの状態が変化すると、React はそのコンポーネントとそのすべての子を再レンダリングします。ただし、useMemo()
、useCallback()
、または React.memo()
による手動のメモ化を適用している場合は除きます。例えば、以下の例では、<FriendList>
の状態が変化すると、<MessageButton>
が常に再レンダリングされます。
function FriendList({ friends }) {
const onlineCount = useFriendOnlineCount();
if (friends.length === 0) {
return <NoFriends />;
}
return (
<div>
<span>{onlineCount} online</span>
{friends.map((friend) => (
<FriendListCard key={friend.id} friend={friend} />
))}
<MessageButton />
</div>
);
}
この例を React Compiler Playground で確認してください。
React Compiler は、手動のメモ化と同等の処理を自動的に適用し、状態の変化に応じてアプリの関連部分のみが再レンダリングされるようにします。これは「ファイングレインリアクティビティ」と呼ばれることもあります。上記の例では、React Compiler は、<FriendListCard />
の戻り値が friends
が変化しても再利用できると判断し、この JSX の再作成を回避し、さらにカウントが変化したときに <MessageButton>
の再レンダリングを回避できます。
コストのかかる計算もメモ化されます
コンパイラは、レンダリング中に使用されるコストのかかる計算も自動的にメモ化できます。
// **Not** memoized by React Compiler, since this is not a component or hook
function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ }
// Memoized by React Compiler since this is a component
function TableContainer({ items }) {
// This function call would be memoized:
const data = expensivelyProcessAReallyLargeArrayOfObjects(items);
// ...
}
この例を React Compiler Playground で確認してください。
ただし、expensivelyProcessAReallyLargeArrayOfObjects
が本当にコストのかかる関数である場合は、React の外部で独自のメモ化を実装することを検討する必要があります。なぜなら、
- React Compiler は、すべての関数ではなく、React コンポーネントとフックのみをメモ化します。
- React Compiler のメモ化は、複数のコンポーネントやフック間で共有されません。
そのため、expensivelyProcessAReallyLargeArrayOfObjects
が多くの異なるコンポーネントで使用されている場合、まったく同じアイテムが渡されたとしても、そのコストのかかる計算は繰り返し実行されます。コードを複雑にする前に、まず プロファイリング を行って、本当にコストがかかるかどうかを確認することをお勧めします。
コンパイラを試してみるべきですか?
コンパイラはまだベータ版であり、多くの未熟な点があることに注意してください。Meta のような企業では本番環境で使用されていますが、アプリの本番環境へのコンパイラの展開は、コードベースの状態と、React のルール をどれだけ守っているかによって異なります。
今すぐコンパイラの使用を急ぐ必要はありません。安定版がリリースされるまで待ってから採用しても大丈夫です。ただし、アプリで小さな実験を試して、コンパイラの改善に役立つ フィードバック を提供していただけると幸いです。
はじめに
これらのドキュメントに加えて、コンパイラに関する追加情報や議論については、React Compiler Working Group を確認することをお勧めします。
eslint-plugin-react-compiler のインストール
React Compiler は、ESLint プラグインも提供します。この ESLint プラグインは、コンパイラとは 独立して 使用できます。つまり、コンパイラを使用しなくても ESLint プラグインを使用できます。
次に、ESLint の設定に追加します。
import reactCompiler from 'eslint-plugin-react-compiler'
export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
]
または、非推奨の eslintrc 設定形式の場合
module.exports = {
plugins: [
'eslint-plugin-react-compiler',
],
rules: {
'react-compiler/react-compiler': 'error',
},
}
この ESLint プラグインは、エディターで React のルールへの違反を表示します。これは、コンパイラがそのコンポーネントまたはフックの最適化をスキップしたことを意味します。これはまったく問題ありません。コンパイラは回復し、コードベース内の他のコンポーネントの最適化を続けることができます。
コンパイラーをコードベースに導入する
既存のプロジェクト
このコンパイラーは、React のルールに従う関数型コンポーネントとフックをコンパイルするように設計されています。また、これらのルールを破るコードについても、それらのコンポーネントやフックを bailout (スキップ) することで処理できます。ただし、JavaScript の柔軟な性質上、コンパイラーは可能なすべての違反をキャッチできるわけではなく、偽陰性 (false negatives) でコンパイルしてしまう可能性があります。つまり、コンパイラーが誤って React のルールを破るコンポーネント/フックをコンパイルし、未定義の動作につながる可能性があるということです。
このため、既存のプロジェクトでコンパイラーを正常に採用するには、まず製品コードの小さなディレクトリで実行することをお勧めします。コンパイラーが特定のディレクトリでのみ実行されるように設定することで、これを行うことができます。
const ReactCompilerConfig = {
sources: (filename) => {
return filename.indexOf('src/path/to/dir') !== -1;
},
};
コンパイラーの導入に自信が持てたら、他のディレクトリにもカバレッジを拡大し、徐々にアプリ全体に展開できます。
新規プロジェクト
新しいプロジェクトを開始する場合は、コードベース全体でコンパイラーを有効にできます。これはデフォルトの動作です。
React 17 または 18 で React Compiler を使用する
React Compiler は React 19 RC で最適に動作します。アップグレードできない場合は、コンパイルされたコードを 19 より前のバージョンで実行できるようにする追加の react-compiler-runtime
パッケージをインストールできます。ただし、最小サポートバージョンは 17 であることに注意してください。
また、コンパイラーの設定に正しい target
を追加する必要があります。ここで、target
はターゲットとする React のメジャーバージョンです。
// babel.config.js
const ReactCompilerConfig = {
target: '18' // '17' | '18' | '19'
};
module.exports = function () {
return {
plugins: [
['babel-plugin-react-compiler', ReactCompilerConfig],
],
};
};
ライブラリでコンパイラーを使用する
React Compiler は、ライブラリをコンパイルするためにも使用できます。React Compiler は、コード変換前の元のソースコードで実行する必要があるため、アプリケーションのビルドパイプラインが使用するライブラリをコンパイルすることはできません。したがって、ライブラリのメンテナーがコンパイラーを使用してライブラリを個別にコンパイルおよびテストし、コンパイルされたコードを npm に出荷することをお勧めします。
コードが事前にコンパイルされているため、ライブラリのユーザーは、ライブラリに適用された自動メモ化の恩恵を受けるためにコンパイラーを有効にする必要はありません。ライブラリがまだ React 19 に対応していないアプリをターゲットにしている場合は、最小限の target
を指定し、react-compiler-runtime
を直接の依存関係として追加してください。ランタイムパッケージは、アプリケーションのバージョンに応じて API の正しい実装を使用し、必要に応じて不足している API をポリフィルします。
ライブラリコードは、より複雑なパターンやエスケープハッチの使用が必要になることがよくあります。このため、ライブラリでコンパイラーを使用することによって発生する可能性のある問題を特定するために、十分なテストを行っていることを確認することをお勧めします。問題が見つかった場合は、'use no memo'
ディレクティブを使用して、特定のコンポーネントまたはフックをいつでもオプトアウトできます。
アプリと同様に、ライブラリのメリットを得るためにコンポーネントまたはフックの 100% を完全にコンパイルする必要はありません。まず、ライブラリの最もパフォーマンスに影響を与える部分を特定し、それらがReact のルールを破っていないことを確認することから始めるのが良いでしょう。これには eslint-plugin-react-compiler
を使用して特定できます。
使用法
Babel
コンパイラーには、ビルドパイプラインでコンパイラーを実行するために使用できる Babel プラグインが含まれています。
インストール後、Babel の設定に追加します。コンパイラーをパイプラインで最初に実行することが重要であることに注意してください。
// babel.config.js
const ReactCompilerConfig = { /* ... */ };
module.exports = function () {
return {
plugins: [
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
// ...
],
};
};
babel-plugin-react-compiler
は、コンパイラーが健全な分析のために入力ソース情報を必要とするため、他の Babel プラグインの前に最初に実行する必要があります。
Vite
Vite を使用している場合は、プラグインを vite-plugin-react に追加できます。
// vite.config.js
const ReactCompilerConfig = { /* ... */ };
export default defineConfig(() => {
return {
plugins: [
react({
babel: {
plugins: [
["babel-plugin-react-compiler", ReactCompilerConfig],
],
},
}),
],
// ...
};
});
Next.js
詳細については、Next.js のドキュメントをご参照ください。
Remix
vite-plugin-babel
をインストールし、コンパイラの Babel プラグインを追加してください。
// vite.config.js
import babel from "vite-plugin-babel";
const ReactCompilerConfig = { /* ... */ };
export default defineConfig({
plugins: [
remix({ /* ... */}),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ["@babel/preset-typescript"], // if you use TypeScript
plugins: [
["babel-plugin-react-compiler", ReactCompilerConfig],
],
},
}),
],
});
Webpack
コミュニティ版の Webpack ローダーがこちらで入手可能になりました。
Expo
Expo アプリで React Compiler を有効化して使用する方法については、Expo のドキュメントをご参照ください。
Metro (React Native)
React Native は Metro 経由で Babel を使用するため、インストール手順についてはBabel での使用セクションを参照してください。
Rspack
Rspack アプリで React Compiler を有効化して使用する方法については、Rspack のドキュメントをご参照ください。
Rsbuild
Rsbuild アプリで React Compiler を有効化して使用する方法については、Rsbuild のドキュメントをご参照ください。
トラブルシューティング
問題を報告するには、まずReact Compiler Playgroundで最小限の再現を作成し、バグレポートに含めてください。 facebook/react リポジトリで issue を開くことができます。
また、React Compiler ワーキンググループのメンバーに応募することで、フィードバックを提供できます。参加の詳細については、READMEを参照してください。
コンパイラは何を想定していますか?
React Compiler は、あなたのコードが以下であることを想定しています。
- 有効なセマンティック JavaScript であること。
- nullable/optional な値とプロパティにアクセスする前に、それらが定義されていることをテストすること(たとえば、TypeScript を使用している場合は
strictNullChecks
を有効にすることで)、つまり、if (object.nullableProperty) { object.nullableProperty.foo }
や、optional-chaining を使用してobject.nullableProperty?.foo
のように記述すること。 - React のルールに従っていること。
React Compiler は React のルールの多くを静的に検証でき、エラーを検出した場合はコンパイルを安全にスキップします。エラーを確認するには、eslint-plugin-react-compiler のインストールもお勧めします。
コンポーネントが最適化されたかどうかを確認するにはどうすればよいですか?
React Devtools(v5.0 以降)には React Compiler の組み込みサポートがあり、コンパイラによって最適化されたコンポーネントの横に「Memo ✨」バッジが表示されます。
コンパイル後に何かが機能しない
eslint-plugin-react-compilerがインストールされている場合、コンパイラーはReactのルール違反をエディターに表示します。これが表示される場合、コンパイラーがそのコンポーネントまたはフックの最適化をスキップしたことを意味します。これは全く問題ありません。コンパイラーは回復し、コードベース内の他のコンポーネントの最適化を続けることができます。すべてのESLint違反をすぐに修正する必要はありません。最適化されるコンポーネントとフックの量を増やすために、自分のペースで対処できます。
ただし、JavaScriptの柔軟で動的な性質により、すべてのケースを包括的に検出することはできません。そのような場合には、無限ループなどのバグや未定義の動作が発生する可能性があります。
コンパイル後にアプリが正常に動作せず、ESLintエラーが表示されない場合は、コンパイラーがコードを誤ってコンパイルしている可能性があります。これを確認するには、"use no memo"
ディレクティブを使用して、関係があると思われるコンポーネントまたはフックを積極的にオプトアウトして問題を解消してみてください。
function SuspiciousComponent() {
"use no memo"; // opts out this component from being compiled by React Compiler
// ...
}
エラーが解消されたら、オプトアウトディレクティブを削除すると問題が再発することを確認してください。次に、Reactコンパイラープレイグラウンドを使用して、バグレポートを共有してください(小さな再現に減らすことも、オープンソースコードの場合はソース全体を貼り付けることもできます)。これにより、問題を特定して修正することができます。
その他の問題
https://github.com/reactwg/react-compiler/discussions/7 を参照してください。