🚀

useEffect の依存配列に条件式を与えてパフォーマンスを向上させる

2023/07/20に公開

useEffectの依存配列は第二引数のカギカッコ”[ ]”内で指定することができます。意外なことに、配列内には変数や関数だけでなく条件式を与えることもできます。条件式を与えることでuseEffectの無駄な発火を抑えることができ、パフォーマンス向上に繋がります。


依存配列に条件式を設定するかどうかで、どのような影響があるのか、具体例で確認していきましょう。

useEffectの依存配列について

まずuseEffectについておさらいします。

useEffectには2つの引数を与えることができます。コールバック関数と依存配列です。

useEffect(コールバック関数,[依存配列])

依存配列のなかには、useEffect”が”依存する値を指定することができます。そして、コンポーネントのレンダリングの後、ここで指定した値に”変化があった”場合にコールバック関数が呼ばれます。

変化の有無をチェックしているに過ぎないので、依存配列の中に条件式を埋め込むこともできます。ご自身の作成したコールバック関数の性質をよく確認して不要な発火を抑制しましょう。

具体例の概要

それでは具体例で確認しましょう。

次のような例を考えてみました。

  1. 黒丸と白丸があります。
  2. 黒丸はキーボードの”WASD” キーを使って上下左右、自由な方向に動くことができます。
  3. 黒丸が白丸に近づきお互いの距離が30ピクセル以下、0.1ピクセル以上になった場合、白丸が黒丸に自動で追尾します

3の実装方法が今回の主題になります。次のようなコードを作成しました。

import { useEffect, useState } from "react";
import { Circle } from "react-konva";
import { useAnimationFrameLoop } from "react-timing-hooks";

export const Enemy: React.FC<
  Pick<IEnemy, "y" | "x" | "oppositeX" | "oppositeY">
> = (props) => {
  const [pos, setPos] = useState({ x: props.x, y: props.y });

  const dx = pos.x - props.oppositeX;
  const dy = pos.y - props.oppositeY;
  const distance = Math.sqrt(dx * dx + dy * dy);

  const loop = useAnimationFrameLoop(() => {
    setPos((prevPos) => ({
      x: prevPos.x - dx * 0.05,
      y: prevPos.y - dy * 0.05,
    }));
  });

  useEffect(() => {
    // console.log(dx, dy, distance);
    if (distance < 50 && distance > 0.1) {
      loop.start();
    }
    return () => {
      loop.stop();
    };
  }, [distance < 50 && distance > 0.1]); <=== !!!

  return <Circle x={pos.x} y={pos.y} stroke={"black"} radius={20} />;
};

コードの最後の方に書いてある矢印の行に注目してください。依存配列の中身に条件式を設定しています。この設定によってuseEffectの発火が条件式に合致する場合にのみ行われます。

Reactの公式ドキュメントには条件式を設定した場合について言及されていなかったと思います。またリンターを有効にした場合、この例だとdistanceが候補に上がってきます。しかしdistanceという変数を設定した場合だと、明らかなパフォーマンスの低下が起きます。実際にどのようなことが起こるのか、変数の場合と条件式の場合で比較をしてみましょう。

パフォーマンスの違い

単なる変数の場合

distanceのみを指定した場合、変数が変化するたびにuseEffectが発火します。次のコードでは先ほどの例の条件式の部分をdistanceに変えてみました。

  useEffect(() => {
    console.log(dx, dy, distance);
    if (distance < 50 && distance > 0.1) {
      loop.start();
    }
    return () => {
      loop.stop();
    };
  }, [distance]); <=== !!!

コンソール出力を有効にして出力結果を確認してみましょう。
下の動画を御覧ください。

黒丸と白丸の距離 「distance」に依存しているので、黒丸が移動するたびにコンソールに出力結果が表示されています。

条件式の場合

続いて条件式の場合を見ていきます。念のためuseEffectの部分のみ、もう一度コードを確認します

  useEffect(() => {
    console.log(dx, dy, distance);
    if (distance < 50 && distance > 0.1) {
      loop.start();
    }
    return () => {
      loop.stop();
    };
  }, [distance < 50 && distance > 0.1]); <=== !!!

コンソール出力で出力結果を確認します。
下の動画を御覧ください。

黒丸が移動してもコンソールに出力結果が現れません。これは、黒丸と白丸の距離が依存配列内の条件式に合致しない場合、useEffectのコールバック関数が呼ばれないからです。また条件式であっても何も問題ないことがわかります。

以上の2つの例から、依存配列に条件式を指定することができること、そして条件式を指定することでパフォーマンスが向上している(低下を防いでいる)ことがおわかりいただけたかと思います。

まとめ

  • useEffectの依存配列に条件式を指定することでパフォーマンスを向上を図れることを示しました。
  • 依存関係を適切に設定して、useEffectをうまく使いこなしましょう!

参考記事、動画

useEffectの依存配列に条件式を設定できることは、Jack Herrington氏の次の動画で知りました。

https://www.youtube.com/watch?v=P95DuIBwnqw&t=704s

useEffectに関して以下の記事を参考にしました。

https://react.dev/reference/react/useEffect

https://qiita.com/uhyo/items/246fb1f30acfeb7699da

https://qiita.com/honey32/items/62edf5165aced7d0c4bf

ソースコード

こちらのCodeSandboxで確認できます。

Discussion