LoginSignup
10
6

More than 1 year has passed since last update.

useEffectのCleanupの使い方まとめ

Posted at

useEffectのCleanupの使い方まとめ

かれこれ2年くらいはReactを使っていたのですが、今までuseEffectのCleanupを意識したことがなかったので、Cleanupの使い方をまとめてみようと思います。
ただし、意識してこなかったからには現場の使い方など知らないので…公式ドキュメントをCleanupに注目して読み、その内容をまとめました。

state/prop依存の非同期処理をする

参考:https://ja.reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

state/propに依存した非同期処理をする場合、適切にcleanupを設定して、prop/stateが変化していないことを確認する必要があります。

  • NG
  const [id, setId] = useState('hoge');
  const [res, setRes] = useState<Response | undefined>();
  useEffect(() => {
    fetch(`https://someurl/${id}`).then((response) => {
      setRes(response);
    });
  }, [id]);
  • OK
  const [id, setId] = useState('hoge');
  const [res, setRes] = useState<Response | undefined>();
  useEffect(() => {
    let ignore = false;
    fetch(`https://someurl/${id}`).then((response) => {
      if (!ignore) setRes(response);
    });
    return () => { ignore = true };
  }, [id]);

cleanupを使用してignoreフラグを確認すれば、次のようなタイミング問題を回避できます。

  • (1)id='hoge'でfetchを開始する。
  • (2)id='fuga'に変わり、id='fuga'でfetchを開始する。
  • (3)先にid='fuga'のfetchが完了
  • (4)後にid='hoge'のfetchが終了
    • →idは'fuga'なのに、最後のsetResはid='hoge'に対する物がセットされてしまう。

※CodePenでちょっとしたデモを作りました。https://codepen.io/charon1212/pen/WNzWypB

setIntervalやsetTimeoutを使う

参考:https://ja.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often

こちらも非同期処理みたいなものですが、setIntervalのようなAPIを使うときには、対応するclear関数でcleanup処理を書きます。

  • NG
  useEffect(() => {
    useInterval(() => {
      // do something;
    }, 1000);
  }, []);
  • OK
  useEffect(() => {
    const id = useInterval(() => {
      // do something;
    }, 1000);
    return () => {
      clearInterval(id);
    }
  }, []);

上記はsetIntervalについての例ですが、setTimeoutも同じようにできるようです。

イベントリスナーを追加する

参考:https://ja.reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

addEventListenerでリスナーを登録するときは、対応するremoveEventListenerをcleanupで書きます。

  • NG
useEffect(() => {
  const handler = () => { alert('hoge'); };
  window.addEventListener('onclick', handler);
}, []);
  • OK
useEffect(() => {
  const handler = () => { alert('hoge'); };
  window.addEventListener('onclick', handler);
  return () => window.removeEventListener('onclick', handler);
}, []);

趣味でreactを使うときはフロント全体をReactで書いてしまうので、EventListenerをいじるような使い方はしないのですが…
いざ使う時が来たら、ちゃんとcleanupしましょう!

ひとつ前のstate/propに関する処理を書く

参考:https://ja.reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

こういった要件に出くわしたことは無いのですが、cleanup関数の中でstate/propを書けば、疑似的に1個前のstate/propが使えます。
実際に出くわした場合、cleanupだけでどうにかなるものなのか、若干怪しい気もしますが…

※次のコードと説明は、参考元のコードで十分わかりやすいため、そのまま引用します。

  useEffect(() => {
    ChatAPI.subscribeToSocket(props.userId);
    return () => ChatAPI.unsubscribeFromSocket(props.userId);
  }, [props.userId]);

上記の例では、userId が 3 から 4 に変わった場合、ChatAPI.unsubscribeFromSocket(3) が最初に走り、その後に ChatAPI.subscribeToSocket(4) が走ります。クリーンアップ関数は「前回」の userId をクロージャとしてキャプチャしていますので、前回の値を取得する必要はありません。

※ドキュメントにも少し残っているように、以前は usePrevious のカスタムフックが書いてありましたが、今ではできるだけ使わないようにするべきなんですね…

最後に

最後まで読んでいただき、ありがとうございました。
今まで意識してこなかった方(主に筆者)は、これを機会に上記のケースだけでもcleanupを適切に書いてみてはいかがでしょうか。
もしここにないプラクティスがあれば、ぜひコメントで教えてください!

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6