React v18でReact Compilerを使う&一部のコンポーネントだけReact Compilerで最適化する方法

投稿日
更新日
タグ
⚛️

はじめに

先日、React Compilerがオープンソースになり、誰でも利用できるようになりました。 しかしながら、React CompilerはReactのベータ版が必要になり、本番環境でガッツリ利用するのは少し難しいです。

そこで、今回はReact CompilerをReact v18でも利用できるようにする方法を紹介します。 また、一度に全てのコンポーネントをReact Compilerで最適化するのは、リスクを伴うため、一部のコンポーネントだけをReact Compilerで最適化する方法も紹介します。

なお、React Compiler自体がまだベータ版であることに留意してください。

React Compilerとは

React Compilerはコンポーネントやフックを自動的に最適化するためのツールです。 具体的にはコンポーネントやフックに含まれる値をメモ化し、不要な再レンダリングを抑制することで、パフォーマンス向上が期待できます。

今まではメモ化するためにユーザーが手動でuseMemo, useCallback, React.memoなどを使う必要がありましたが、React Compilerを使うことで、これらの手間を省くことができます。

React Compilerはその名の通り、コンパイラとして動作します。 具体的にはビルドツールと連携することで、ユーザーが書いたコードそのものを変換します。

現在はBabelのプラグインとしてのみ提供されていますが、今後はRustでの実装になるようです。1

React Compiler Playgroundでコンポーネントのコードをコンパイルした例

React Compilerの導入方法

公式ドキュメントに記載の通りですが、一応導入方法を紹介します。 なお、今回はViteを使います。

まず、React CompilerのBabel実装であるbabel-plugin-react-compilerをインストールします。

npm install -D babel-plugin-react-compiler

次に、vite.config.jsに以下のように設定します。

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

"babel-plugin-react-compiler"の隣の空オブジェクトはオプションです。React 18でReact Compilerを使う場合は追加の設定が必要なので、後で解説します。 Reactのbeta版を使う場合はこれで設定は完了です。

React v18でReact Compilerを使う

ここからはReact v18でReact Compilerを使うための設定方法を紹介します。 まず、React Compilerではコンパイル後のコードにcという関数が追加されるため、それを追加する必要があります。2

c関数はReactのbeta版には含まれていますが、React 18には含まれていません。 babel-plugin-react-compilerでは自分で定義したc関数を使うように設定することができるため、まずは自分でc関数を定義します。

import { useState } from "react";

const $empty = Symbol.for("react.memo_cache_sentinel");

/**
 * DANGER: this hook is NEVER meant to be called directly!
 *
 * Note that this is a temporary userspace implementation of this function from
 * React 19. It is not as efficient and may invalidate more frequently than the
 * official API. Please upgrade to React 19 as soon as you can.
 */
export function c(size: number) {
  return useState(() => {
    const $ = new Array(size);
    for (let ii = 0; ii < size; ii++) {
      $[ii] = $empty;
    }
    // @ts-ignore
    $[$empty] = true;
    return $;
  })[0];
}

このc関数はこちらのGistからお借りしました。 なおこれはReactのベータに含まれるreact-compiler-runtimeと同じです。

これをsrc/react-compiler-runtime.tsとして保存します。後でパスを指定しますが、ファイル名や場所は自由です。

次に、vite.config.jsを以下のように修正します。

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            "babel-plugin-react-compiler",
            {
              runtimeModule: path.resolve("src/react-compiler-runtime"),
            },
          ],
        ],
      },
    }),
  ],
});

runtimeModuleオプションに先ほど作成したsrc/react-compiler-runtime.tsのパスを指定します。 これでReact Compilerがビルド時に自分で定義したc関数を使うようになります。

これでReact 18でもReact Compilerを使う準備が整いました。 後はいつも通りにビルドすれば、React Compilerがコンパイル時にコンポーネントを最適化してくれます。

なお執筆現在、@vite/plugin-react v4.3.0にはruntimeModuleオプションを指定した際にエラーが発生するバグがあります。v4.2.1にダウングレードすることで回避可能です。 詳細はこちらのIssueを参照してください。(PRも立っているので、そのうち修正されると思います)

一部のコンポーネントだけReact Compilerで最適化する

さて、ここまではReact Compilerを動かす方法を紹介してきました。 しかし、一度に全てのコンポーネントをReact Compilerで最適化するのはリスクが伴います。 特に、React Compilerはまだベータ版であり、いきなり全てのコンポーネントを最適化すると予期せぬバグが発生する可能性があります。 一応、React CompilerはReactのルールに則っているコンポーネント・フックのみを最適化するようになっていますが、それでもリスクはゼロではありません。

徐々にReact Compilerを導入していくためにbabel-plugin-react-compilerではコンパイラの最適化をOpt-inにすることができます。 まずは、vite.config.jsの設定を以下のように変更します。

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            "babel-plugin-react-compiler",
            {
              runtimeModule: path.resolve("src/react-compiler-runtime"),
              compilationMode: "annotation",
            },
          ],
        ],
      },
    }),
  ],
});

compilationModeオプションに"annotation"を指定することで、React Compilerの最適化をOpt-inにすることができます。 このオプションを設定すると、コンポーネントに"use memo"というディレクティブを追加しないとReact Compilerが最適化を行いません。

function MyComponent() {
  "use memo";

  return <div>"Hello, world!"</div>;
}

このように、コンポーネントの先頭に"use memo";と書くことで、そのコンポーネントだけをReact Compilerで最適化することができます。 これによって、徐々にReact Compilerを導入していくことができます。 なお、このディレクティブはReact Compilerの移行期にのみ使うように想定されており、長期的な利用は推奨されていません。

詳しくは公式ドキュメントを参照してください。

まとめ

今回はReact v18でReact Compilerを使う方法と、一部のコンポーネントだけをReact Compilerで最適化する方法を紹介しました。 React Compilerはまだベータ版であり、本番環境で利用するのはリスクが伴いますが、徐々に導入していくことでパフォーマンス向上が期待できます。

また、React Compilerの中身を@yossydev@re_taro_と一緒に読む動画も公開しているので、興味があればぜひご覧ください。

React Compiler Code reading #1

参考


脚注

  1. 現に、Reactのリポジトリにはbabelの実装コードと、Rustの実装コード(まだ未完成)が存在しています。

  2. 実際には_cという関数になっていますが、ベータ版に含まれるc関数の実装であるreact-compiler-runtimeにはcとして定義されているため、ここではcとして説明します。

Read next