Open4

React Compilerについて調べてみる

弊社弊社

React公式「React Compiler」

現在useMemoやuseCallbackで手動で実施しているコードのメモ化を自動で実行してくれるコンパイラー。
コードベースがよくメモ化されているプロジェクトではあまり価値が発揮できない可能性があるが、現実的にはほとんどのプロジェクトで効果が見られるだろう(ここまで言ってない)と書いてある。

弊社弊社

React Compilerは、以下の条件が満たされていることが前提で動作する。

  • 有効でセマンティックな JavaScript であること。
  • nullチェックを行っていること
    • TypeScriptのstrictNullChecksモードが有効になっているなど
  • React のルールに従っていること。
    • コンポーネントとフックが純粋
    • コンポーネントとフックが正しく利用されていること
      • コンポーネントが通常のカンストして呼び出されていない
      • フックがコンポーネント内でのみ呼び出されている
      • フックが通常の値のように渡されていない
        • フックが関数やコンポーネントに渡されていない
      • フックが動的に変更されていない(高級関数として使用されていない)
      • フックを動的に使用していない

react-compiler-healthcheckやeslint-plguin-react-compilerを利用することで、既存のプロジェクトがReact Compiler導入可能かどうかチェックできるっぽい。
また、自動的にReactのルールを守っているコードのみに適応するらしいが、全てのルール違反を自動で摘出することはできない。

既存コードベースで有効にする場合はReact Compilerのルールセットで小さなディレクトリのみ対象とするようにして、徐々に範囲を拡大していくよと良いらしい。
"use memo"と"use no memo"というディレクティブを使うことでもコンパイル対象をコントロールできるっぽい。

弊社弊社

メモ化の実施によってどの程度際レンダリングが抑えられるのか、React Dev Toolで確認できるという噂があるので、実際に試してみる.

弊社弊社

試し見たらちゃんと最適化されて良かった。
実装が適当すぎて、そこまでハッキリ感じられるものじゃ無いけど、頑張ってmemoとか書かなくても良くなるのは(今もやってないけど)良いですね。

試した実装
import { SetStateAction, useState } from "react";
import { Todo } from "./types";

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);

  return (
    <div style={{ padding: "12px" }}>
      <Title title="React Compiler" />
      <TodoAdd setTodos={setTodos} />
      <TodoList todos={todos} setTodos={setTodos} />
    </div>
  );
}

const Title = (props: { title: string }) => {
  const { title } = props;

  return <h1>{title}</h1>;
};

const TodoAdd = (props: {
  setTodos: React.Dispatch<SetStateAction<Todo[]>>;
}) => {
  const { setTodos } = props;
  const [text, setText] = useState("");

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    if (text === "") return;
    setTodos((todos) => [...todos, { id: todos.length, text }]);
    setText("");
  };

  return (
    <form>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={handleClick} type="submit">
        追加
      </button>
    </form>
  );
};
const TodoList = (props: {
  todos: Todo[];
  setTodos: React.Dispatch<SetStateAction<Todo[]>>;
}) => {
  const { todos, setTodos } = props;
  const getHandleClick = (id: number) => () => {
    setTodos((todos) => todos.filter((todo) => todo.id !== id));
  };

  return (
    <ul>
      {todos.map(({ id, text }) => (
        <li key={id}>
          {text}
          <button onClick={getHandleClick(id)}>削除</button>
        </li>
      ))}
    </ul>
  );
};

export default App;