Yuto Blog
speaker-deck-iconyoutube-iconrss-iconx-icongithub-icon

'Introduce to React Compiler' blog.

目次

Intro

このブログは8/4に行われた若手エンジニアLT & 交流会というイベントで発表した、Introduce to React Compilerという資料の日本語ブログです。 資料は基本英語なので、翻訳だと思っていただければ大丈夫です。

話さないこと

今回は以下のことについては話さないです。

あと資料の中では自己紹介も入っていますが、このブログでは書かないです。

React Compilerとは

React Compiler is a compiler that optimizes React applications, ensuring that only the minimal parts of components and hooks will re-render when state changes. The compiler also validates that components and hooks follow the Rules of React.

ref: https://github.com/facebook/react/tree/main/compiler#react-compiler

日本語に訳して説明をすると、つまりReact Compilerとは、Reactアプリの最適化を行うコンパイラのことを指します。

皆さんはこれまでReactアプリの最適化を行う際に、

のようなReactが提供しているメモ化関数を使って手動で最適化を行ってきたと思います。React Compilerを使えば、自動で最適化を行ってくれるようになるため、開発者によるメモ化は不要になります。そしてこれはReact19でやってやってきます!

Reactのレンダリングの特徴

Reactは基本的には全て再レンダリングするような仕組みになっています。Reactはと言っているのは、SolidJSなどの他のフレームワークでは再レンダリングを行うタイミングを開発者が制御するようになっています。 Reactではそれが反対で、再レンダリングしないように制御するようになっています。そのため更新したかどうかを判断する必要がなく、基本的には常に最新のデータが表示されるようになっています。

React Compilerがこのタイミングで登場した背景として、コンパイラの必要性を認識するのがそもそも後になり、さらに実際に作るのにも時間がかかったそうです。

React Compilerを入れることで嬉しいこと

React CompilerはReactアプリの最適化を自動で行ってくれるものということがわかりました。 では次に、実際にReact Compilerを入れることで我々開発者はどんなことが嬉しいのでしょうか。筆者は二つポイントがあると思っています。

それぞれ見ていきましょう。

手動メモ化からの解放

当然ですが自動のメモ化がメリットです。

import { useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');

  const handleSubmit = (orderDetails) => {
    setAddress("122-2222")
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  };j

  const visibleTodos = () => filterTodos(todos, tab);

  return (
    <>
      <p>{address}</p>
      <Greeting name={"Yuto"} />
      <button onClick={visibleTodos}>Click!!</button>
      <button onClick={handleSubmit}>Click!!</button>
    </>
  );
}

function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
};

こちらはuseMemo/useCallback/React.memoをそれぞれ使ったみたコードになります(ドキュメントから引っ張ってきたので、若干非現実的なコードにはなっています🙏) これからのReactではこのメモ化関数を使用する必要はなくなります。

そして次に、React Compilerを使ってコンパイルした結果は以下のようになります。

function MyApp() {
  const $ = _c(8);
  useState("");
  const [address, setAddress] = useState("");
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = (orderDetails) => {
      setAddress("122-2222");
      post("/product/" + productId + "/buy", {
        referrer,
        orderDetails,
      });
    };
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  const handleSubmit = t0;
  const visibleTodos = _temp;
  let t1;
  if ($[1] !== address) {
    t1 = <p>{address}</p>;
    $[1] = address;
    $[2] = t1;
  } else {
    t1 = $[2];
  }
  let t2;
  let t3;
  let t4;
  if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
    t2 = <Greeting name="Yuto" />;
    t3 = <button onClick={visibleTodos}>Click!!</button>;
    t4 = <button onClick={handleSubmit}>Click!!</button>;
    $[3] = t2;
    $[4] = t3;
    $[5] = t4;
  } else {
    t2 = $[3];
    t3 = $[4];
    t4 = $[5];
  }
  let t5;
  if ($[6] !== t1) {
    t5 = (
      <>
        {t1}
        {t2}
        {t3}
        {t4}
      </>
    );
    $[6] = t1;
    $[7] = t5;
  } else {
    t5 = $[7];
  }
  return t5;
}
function Greeting(t0) {
  const $ = _c(3);
  const { name } = t0;
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  const t1 = name && ", ";
  let t2;
  if ($[0] !== t1 || $[1] !== name) {
    t2 = (
      <h3>
        Hello{t1}
        {name}!
      </h3>
    );
    $[0] = t1;
    $[1] = name;
    $[2] = t2;
  } else {
    t2 = $[2];
  }
  return t2;
}
function _temp() {
  return filterTodos(todos, tab);
}

cという引数の数字分の配列を作成し、条件分岐でその中に値やjsxを格納していっています。

(cについては以前筆者がブログ書いたのでぜひそちらをご覧ください。React Compilerで出力されるc(number)について

筆者としてはメモ化するしないのコミュニケーションが減ることも良いと思っています。 よく記事でも「こういったケースはメモ化した方が良い」「メモ化は全部はしないほうが良い」というような記事が定期的に出ていたり、業務の中でも「これは〇〇だからメモ化は逆にしないほうが良いのでは」といったレビューをもらった開発者の方は多いのではないでしょうか。筆者も何度かレビューをもらったことがありますが、正直アプリケーションを作る上でメモ化するしないの判断をするのは難しいと思っていたので、自動メモ化の恩恵はこういった部分でも役に立つと思っています。

コンパイルチェックによるコードの品質担保

React Compilerを使用することで意味解析が可能になり、これにより意図してない/許可していないコードがあればコンパイルが失敗するようになっています。

例えば以下のようなコードは、コンパイルエラーとなります。

// ref: https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-via-for-of-iterator.expect.md
function Component(props) {
  const items = [];
  for (const x of props.items) {
    x.modified = true; // InvalidReact
    items.push(x);
  }
  return items;
}

ぜひこのコードをPlaygroundで試してみてください。InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable insteadというエラーになるはずです。しかしこのコードは今までは特に問題なくブラウザでも動作しました。

React Compilerを使うことで、このように事前に安全ではない、意図していないコードを検知することができます。

ちなみに、コンパイル時ではなく編集中にエラーを知りたい!という方には、eslint-plugin-react-compilerというプラグインがあります。これを使えばReact Compilerによって検出されたエラーをlintとしてチェックすることもできます。React Compilerを使うのであれば、一緒に入れておくと良いでしょう。

React CompilerのHIRについて

React Compilerのコードを読んでいると、HIR(High-level Intermediate Representation)という概念が登場します。ここでは、HIRについて簡単に説明します。(本来はここでコンパイルの流れを書きたかったんですが全体像まだ追えていないので別の機会に🙏)

HIRとは

HIRは、高レベルなコード構造を保持しつつ、コンパイラが扱いやすい形式に変換したものです。これはRust Compiler Development Guideから影響を受けています。

HIRはReact Compilerでは以下のように生成されます。

  1. Babelを使ってコードをパース
  2. ASTを生成
  3. ASTをlowering(低レベル化)してHIRを作成

なぜHIRなのか

そしてこのHIRを使うことで、以下のようなメリットがあります。

High-level code is more compact, and helps reduce the impact of compilation on application size

High-level constructs that match what the developer wrote are easier to debug

日本語に訳すと以下のようになります。

例えばJavaScriptの構文に論理式というものがあります。

(a ?? b)

これは役割的にはaがnullabeだった場合bを使用するというif分的役割を果たすのでif分になるんですが、React Compilerでは先ほどの二つの理由によってこの論理式のまま扱いたい、そのためにHIRを使ったと言うことらしいです(厳密にはもっと理由はあると思いますが👀)

まとめると、HIRは、既存のコードに近い状態(高レベルな状態)でコンパイラが扱えるようにするもの。そしてReact Compilerではコンパイル後の影響を最低限に、デバッグの容易さ、という二つのポイントからHIRを採用したといことです。面白いですね。

まとめ

React 19は久しぶりのReactのメジャーアップデートになります。React Compilerが入ることで実際にどのような体験を開発者が得られるのかとても楽しみです。

最後に宣伝にはなりますが、React Comiler Code ReadingというYoutubeでCompilerのコードを読む会をしているので、気になった方やコンパイラについて理解を深めたい方はぜひ見てみてください🫣

発表で話した内容のメモ

関連

0