useEffectの第2引数には何を指定すればよいのか

https://ja.reactjs.org/docs/hooks-reference.html#useeffect

useEffectの第2引数として配列を渡すことで配列に指定した値それぞれに変更があった場合のみuseEffect内の関数が実行されます。

useEffect内でなにか関数を実行する際、第2引数として関数も渡すことがあると思いますが、これが正しいのかどうか分からず調べてみました。

例えばAPIから取得したデータをstateにセットするみたいなコードがあったとします(コードは適当です)

const Component = () => {
  const [count, setCount] = useState(0);
  const { response } = useAPI();
  
  useEffect(() => {
    setCount(response.length);
  }, [response, setCount]);   // ここでsetCountって必要?
}

このset関数はuseEffectの第2引数に含める必要があるのでしょうか?結論から言うと↑のコードでは setCount はセットする必要はありません。

useStateのドキュメントに以下のような記述があり、再レンダリングされた場合もset関数は同一性が担保されておりuseEffectの依存リスト(第2引数)に含めても値が変わることがないので入れる必要がありません。

同様にuseReducerも同じような記述がありdispatchも入れる必要がありません。

通常の関数は再レンダリング時に同一性がない

以下のように書くと、関数で実行する内容は変わらないにもかかわらず再レンダリングされるたびにuseEffectが実行されします。

const Component = () => {
  const [count, setCount] = useState(0);

  const hello = () => {
    console.log('hello!');
  }
  
  useEffect(() => {
    hello();
  }, [hello]);
}

これは再レンダリングのたびに関数が再生成されるため、変更があったかどうかを判断するObject.is()はfalseとなりuseEffectが実行されます。↓のイメージです。

Object.is(() => null, () => null);  // => false

上記の場合どうするのがいいのかというと、A Complete Guide to useEffectによれば、propsやstateに依存しない関数の場合はコンポーネントの外で関数を定義し、依存する場合はuseEffect内で関数を定義するのがよいとのこと。

// コンポーネントの外で定義
const hello = () => {
  console.log('hello!');
}

const Component = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    hello();
  }, []);
}
const Component = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // useEffect内で定義
    const hello = () => {
      console.log(data);
    }

    hello();
  }, [data]);
}

useCallbackを使って関数自体をメモ化して再レンダリングされた場合でも同一のものを返すということもできますが、公式ドキュメントには最終手段と書いているので、上記2つで書くことができない場合に使うとよさそうです。

const Component = () => {
  const [count, setCount] = useState(0);

  const hello = useCallback() => {
    console.log(data);
  }, [])

  useEffect(() => {
    hello();
  }, [hello]);
}

ESLintのeslint-plugin-react-hooksプラグイン

ESLintのeslint-plugin-react-hooksプラグインを利用しreact-hooks/exhaustive-depsのルールをONにすることで、useEffect, useCallbackの第2引数を自動でチェックすることができます。

上記のuseStateのset関数やuseReducerのdispatch関数、useRefのrefはあらかじめ除外されており、チェックされないので含める必要がありません。また、通常の関数についても再レンダリングのたびにuseEffectが実行される場合はlintで教えてくれます。

カスタムフックに処理を出している場合に注意が必要

カスタムフック内で関数を定義し、それをコンポーネント側で利用するときはこのようなコードになるかと思います。

const useHello = () => {
  const hello = (count) => {
    console.log('hello! count', count);
  }
  return { hello };
}

const Component = () => {
  const [count, setCount] = useState(0);
  const { hello } = useHello();

  useEffect(() => {
    hello(count);
  }, [count, hello]);
}

このコンポーネントが再レンダリングされたとき、countが変更されているいないに関わらずuseEffectが実行されます。この場合はESLintのreact-hooks/exhaustive-depsでも検知することはできないため、カスタムフックの実装時に関数は同一性が担保されるように実装する必要があります。

関数の実行結果を第2引数に指定してもよいか

あまり書かないでしょうがこんなコードです。

const neededCountUp = () => {
  return true;
};

const Component = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(prev => prev + 1);
  }, [neededCountUp()]);
}

これも結論から言うと動作はしますが、当然再レンダリング時に前回の情報とのObject.is()で変更があったかを判断するため、↑のコードでは関数の返り値として常にtrueが返ってきており2回目以降のレンダリング時にはuseEffectは実行されしません。再レンダリング時に関数が毎回実行され返り値からuseEffectを実行させるか判断するという挙動となりあまり効率は良くないコードかもしれません。

ちなみに↑のようなコードではESLintoのreact-hooks/exhaustive-depsではチェックにかかり、関数実行の返り値の別の変数にいれましょうというメッセージが出ます。

Photolog 4/25

今日も単焦点レンズ。ずっとF1.8 ISO100で撮ってみて、シャッタースピードはAUTO。シャッタースピード1/800〜1/4000くらいだったので、もうちょっと長くして明るく撮るのもよさそう。
あとは被写体を真ん中に起きがちなので空をいれてみるとか子供の視線の先を入れてみるとかしてみよう。

ブログ移行 & デジタルミニマライズ

ここ2年くらいはミニマルに生活しようと思って家のものをどんどん捨てたり、洋服もできるだけ少なくして制服化していたけど、デジタルなものに関してはできていないなと思い立ったのがきっかけ。

技術的なtipsはQiita/zenn.dev、ちょっと長文ははてなブログ、日記的なものはnoteみたいな感じにしようかなと思っていたけど、あれこれ管理するとその分脳内コストも掛かるので1つにまとめるためにWordPressでブログを立ち上げ移行した。あとは各サービスにいいね機能などもあるからいいねが欲しくなってしまうし、あまり気にせずいろいろ書きたいというのもあった。

SNSやツールなどのアカウントも残っていたものもあまり使っておらずなくても困らないだろうというものはできる限りアカウント削除した。

  • Facebook
  • Instagram
  • Tumblr
  • Foursquare
  • Qiita
  • Zenn.dev
  • note
  • Notion
  • Pocket
  • Todoist
  • Trello
  • 転職系のサービス

Twitterは連絡をとったりすることがあるので残している。メモはGoogle Keepに雑多に残すで全然大丈夫そう。

WordPress、CDNとか画像圧縮とかいろいろ設定したけど結構早い!