🎃

React18の新機能(Automatic Batching)の使い方を確認

2023/09/17に公開

はじめに

今回は React18 の新機能Automatic Batchigについて React17 と React18 それぞれの動作の違いについて確認していきたいと思います。
Transition についてはこちらの記事をご覧ください。
https://zenn.dev/rh820/articles/686701ba31ed44

動作確認用リポジトリ

https://github.com/dahoho/react-18-demo

Automatic Batching とは?

React 18 では、デフォルトでより多くのバッチ処理を実行することで、すぐに使用できるパフォーマンスの向上が追加されています。バッチ処理とは、パフォーマンスを向上させるために、React が複数の状態更新を 1 つの再レンダリングにグループ化することです。React 18 より前は、React イベント ハンドラー内で更新をバッチ処理するだけでした。Promise、setTimeout、ネイティブ イベント ハンドラー、またはその他のイベント内の更新は、デフォルトでは React でバッチ処理されませんでした。

(引用元:https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching)

Automatic Batching は、複数の setState などが呼ばれた場合に、一度のレンダリングでまとめて処理する機能です。
React17 までは、関数内でのみバッチ処理が行われていましたが、React18 からは関数外でもバッチ処理が行われるようになりました。
promise や setTimeout、ネイティブイベントハンドラーなどの関数外での処理もバッチ処理が行われるため、パフォーマンスが向上しました。
まずは、React17 でも行われている関数内でのバッチ処理を確認していきます。

関数内でのバッジ処理

更新ボタンをクリックすると、コンソールに foo の値が 2 回出力されるコンポーネントです。

EventHandler.tsx
import { useState } from "react";

const EventHandler = () => {
  console.log("EventHandlerコンポーネントがレンダリングされました!");
  const [foo, setFoo] = useState<number>(0);
  const [bar, setBar] = useState<number>(0);

  const countUp = () => {
    console.log(foo);
    setFoo((prev) => prev + 1);
    console.log(foo);
    setBar((prev) => prev + 1);
  };

  return (
    <div className="mt-8">
      <p className="text-center">イベントハンドラ内の処理</p>
      <div className="mt-6 text-center">
        <div className="flex justify-center gap-5">
          <p>foo: {foo}</p>
          <p>bar: {bar}</p>
        </div>
        <button
          className="bg-slate-600 text-white py-2 px-5 mt-5"
          onClick={countUp}
        >
          更新
        </button>
      </div>
    </div>
  );
};

export default EventHandler;


実行結果

コンソールを確認すると、以下のようになります。
ボタンをクリックすると、同じ値が 2 回出力された後にコンポーネントが再レンダリングされることが確認できます。
これによって、setState がバッチ処理(1 つにまとめられている)されていることがわかります。
バッチ処理が行われない場合、setState の記述を増やすごとにコンポーネントが再レンダリングされてしまい、パフォーマンスが低下します。

関数外でのバッジ処理(React18 新機能)

関数外でのバッジ処理は React18 からの新機能です。
fetch で jsonplaceholder からデータを取得して表示するコンポーネントで確認していきたいと思います。
バッチ処理が行われているか確認するため、分かりやすいようにsetIsVisible((prev) => !prev);を 3 つに増やして、React17 と React18 での挙動の違いを確認していきたいと思います。

EventHandler.tsx
import { useState } from "react";

type TodoType = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

const Other = () => {
  console.log("Otherコンポーネントがレンダリングされました!");
  const [todosData, setTodosData] = useState<TodoType[] | null>(null);
  const [isVisible, setIsVisible] = useState<boolean>(false);

  const fetchApi = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/todos");
    const data = await res.json();
    setIsVisible((prev) => !prev);
    setIsVisible((prev) => !prev);
    setIsVisible((prev) => !prev);
    setTodosData(data);
  };

  return (
    <div className="mt-8">
      <p className="text-center">イベントハンドラ以外の処理</p>
      <div className="mt-6 text-center">
        <button
          className="bg-slate-600 text-white py-2 px-5 mt-5"
          onClick={fetchApi}
        >
          API取得
        </button>
        {isVisible && (
          <div className="mt-8">
            {todosData?.map((todo) => (
              <p key={todo.id} className="p-2">
                {todo.title}
              </p>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

export default Other;


実行結果

コンソールを確認すると、以下のようになりました。
React17 では、関数外でのバッチ処理は行われていないため、setState の回数だけコンポーネントが再レンダリングされていることがわかります。
一方、React18 では、関数外でもバッチ処理が行われているため、コンポーネントが無駄に再レンダリングされていないことがわかります。

React17

React18

まとめ

React17 と React18 のそれぞれの関数内バッチ処理と関数外でのバッチ処理の違いを確認しました。
自動バッチングは普段開発で意識しなくても有効化されている機能ですが、理解しておくべき機能だと思います。
今後は Transition や Suspense などの機能についても確認していきたいです。

参考文献

https://ja.legacy.reactjs.org/blog/2022/03/29/react-v18.html
https://www.udemy.com/course/react_v18/

GitHubで編集を提案

Discussion