monologue

クズエンジニアの独白

useEffect 覚え書き

useEffect について理解な曖昧な部分があったので改めて調べた。

TL;DR

  • useEffectレンダリング後に処理(副作用)するときに使う
  • 第2引数に依存値を設定する。 [] と空にすると初回レンダー時のみ、第2引数自体を設定しないと毎レンダリング後に実行される
  • DOMの画面反映前に処理を実行したい場合は useLayoutEffect を使う
  • useEffect の返り値にはクリーンアップ関数を指定できる
  • クリーンアップ関数はReact17で仕様が変更され、常に次のレンダリング後のDOM状態が画面反映された後に実行されるようになった

useEffect の基本

useEffect は、ReactのHooksの一つで、レンダリング後に何かの処理(公式ドキュメントでは副作用と呼ばれてる)をしたいときに使う。

useEffect(() => {
    // 処理
}, [ hoge(依存値) ]);

レンダリング後に呼び出されるため、useEffect が実行される際は、DOMは正しく更新されていることが保証されてる。

レンダリングで呼び出される必要がない場合は、第2引数に依存する値(変更があったらuseEffectを処理したい値)を設定できる。初回レンダリング後のみで呼び出したいときは、空配列[]を設定。

useEffectレンダリング後に非同期で実行されるので UX 観点では良い傾向がある。 ただ、時には同期的に処理をしたい(useEffectを実行した後に画面に反映したい)ときがある。その時は似たHooksの useLayoutEffect を使える。ただ、もちろんその分画面反映が遅れるのでご利用時は注意が必要。

クリーンアップ関数について

useEffectは必須ではないが戻り値としてクリーンアップ関数を設定できる。クリーンアップ関数は自分は未だにあまり使ったことがないが、後始末的な位置付けで必要な処理のよう。

公式ドキュメントの受け売りだが、例えば、外部APIの購読に対して、クリーンアップ関数は解除をする処理を書くなど(ときには後始末をしないことでメモリリーク等が起こる可能性があり、それを防止するためにクリーンアップする)。

hooksがない時代は、Reactのライフサイクル毎(例えば componentDidMountcomponentWillUnmount )に書く必要があり、関連性の高いコードが分散したり、記述し忘れによるバグ発生が頻発してたらしい。

ちなみに公式ドキュメントを拝借するがコード例。

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

そして、このクリーンアップ関数が、最近React17によって破壊的変更があったらしい。 詳しくは下記の記事に分かりやすくまとまっている。

要はReact17では常に次のレンダリング後のDOM状態でクリーンアップ関数が実行されるようになったとのこと。ちなみにReact16までは、アンマウント時なのか再レンダリング時なのかでDOMの状態が異なっていた。なので統一感が出て改良されたが、破壊的変更なので気をつけないとねということ。

zenn.dev

useEffect の使い方

useEffect は処理のタイミングを依存値によって制御することがよくあり、正しく使わないと思わぬバグや不要なレンダリングを行いパフォーマンスを損ねるケースがよくあるので注意が必要。

下記の記事が参考になった。

qiita.com

個人的にまとめると、

  • useEffect 内でしか使わない処理は関数化するにしても useEffect 内に入れてしまう(関数が useEffect の第2引数の値以外にから影響を受けないことを保証できる)
  • 複数の useEffect でpropsやstateに依存する関数を共通で呼び出す必要がある場合は、useCallback を利用して関数を定義し、複数の useEffect で使うと良さそう
  • そもそもpropsやstateに依存しない関数はコンポーネントの外部に書く(内部に書いてもいいが副作用処理になることがほとんどなので、結局 useEffect で依存配列を [] に設定する感じになるので、外だしした方が無駄な処理も減り、可読性も上がる)