🐡

ReactでRerenderが分かるようにする

に公開

始めに

Reactではパフォーマンスが非常にシビアで、何も考えないと毎回rerenderされてしまい動きがかなり重くなる可能性があります。しかしどのコンポーネントがいつrerenderが走っているかパッと見ることができないため中々気づけません。rerenderの検出はReact Developer Toolsで以下の記事などを参考することで確認することができます。

https://dev.classmethod.jp/articles/react-developer-tools/

上記の設定でほとんどは事足りるかもしれませんが、CodeSandbox上だと上手く確認できなかったり、render回数を表示したりなど自分で色々いじりたい場合は自作した方が良いので、その方法について備忘録としてまとめました。

renderの回数を表示する

renderの回数を表示するには、単純にコンポーネントのrenderメソッドを呼ばれた回数をカウントすると良いです。ただ useState だと更新メソッドを呼ぶとまたrerenderしてしまうため、useRefでライフサイクルに影響を与えないようにします。

renderの回数を表示するコンポーネント
import { FC, useRef } from "react";

export const RenderCount: FC = () => {
  const renderCountRef = useRef(0);

  renderCountRef.current += 1;

  return <div>render count: {renderCountRef.current}</div>;
};

render時にアニメーション表示する

render回数が表示されるのは良いことですが、React.StrictModeでrenderしている場合は余計にrenderされて本番でrenderされる回数が分かりづらくなってしまいます。単純にrerenderされたかを可視化するには以下の記事のようにフェードで表示するのが良さそうです。ただ具体的な実装がこの記事からでは見つけられなかったので自己流で実装しました。

https://alexsidorenko.com/blog/react-render-always-rerenders/

CSSアニメーションで実装

まず最初に思い浮かぶのはCSSアニメーションです。以下のようなCSSアニメーションを設定したらフェード表示できそうです。

フェードアニメーション
@keyframes fadeOut {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translateY(-10px);
  }
}

.Item {
  animation: fadeOut 1s forwards;
  color: red;
}

あとはrerenderするたびにこのアニメーションが発火すれば良いですが、アニメーションだけ発火させる方法が分からなかったので key を更新してDOM自体を作り直す方法にしました。前のセクションで紹介したrender回数をカウントするやり方を使ってその数を key に設定することで毎回違うDOMを生成してくれるようになるため、目的を達成することができます。

keyを更新してCSSアニメーションを毎回実行させる
import { FC, useRef } from "react";

import styles from "./NotifyRerender.module.scss";

export const NotifyRerender: FC = () => {
  const renderedCountRef = useRef(0);

  renderedCountRef.current += 1;

  return (
    <div
      // keyを変更して毎回DOMを作り直してアニメーションを再発動させる
      key={renderedCountRef.current}
      className={styles.Item}
    >
      Rerender
    </div>
  );
};

Web Animations APIで実装

CSSアニメーションの場合だとCSSファイルを用意したり、keyでDOMを再生成したりする必要があって割と手間だったので別な方法も考えました。JSでアニメーションをする場合は今だとWeb Animations APIというものが使えるため、そのやり方も試してみました。
useEffectで第二引数を渡さない場合は毎回実行される仕様を活かし、DOMさえ取得できればeffectするたびにanimateメソッドを実行するだけで良さそうです。

Web Animations APIを使ってフェードアニメーション表示する
import { FC, useRef, useEffect } from "react";

export const NotifyRerenderByWebAnim: FC = () => {
  const elRootRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (elRootRef.current == null) {
      return;
    }

    elRootRef.current.animate(
      [
        { opacity: 1, transform: "translateY(0)" },
        { opacity: 0, transform: "translateY(-10px)" }
      ],
      {
        duration: 500,
        fill: "forwards"
      }
    );
  });

  return (
    <div ref={elRootRef} style={{ color: "red" }}>
      Rerender
    </div>
  );
};

動作確認

CodeSandboxで検証コードを書きましたので以下に貼っておきます。このサンプルではrender回数が余分に増えると分かりづらかったのでReact.StrictModeは消しています。また一つだけコンポーネントをメモ化してrerenderが抑制されているかも確認できるようにしています。

終わりに

以上がReactのrerenderを可視化する方法でした。Reactはパフォーマンスがシビアに出るので自前でも確認する術を知れて良かったです。自作コンポーネントを配置する場合は環境変数で開発中だけ表示するようにしたら本番に影響は出ないと思うのでパフォーマンスをシビアにチェックしたい場合に仕込んでおくと良いかもです。
基本的にはReact Developer Toolsを設定するだけで十分そうですが、もし自作コンポーネントを配置して確認できるようにしたい時がありましたら参考にしていただけると幸いです。

GitHubで編集を提案

Discussion