Kodokon kodokon.com

useEffect: 副作用、依存配列、クリーンアップ

useEffect を使ってコンポーネントを外の世界と同期させ、依存配列とクリーンアップを使いこなします。

9 分 · 3 問

このレッスンを Kodokon で開く

React コンポーネントは props と state から JSX を計算します。そのスコープの外にあるもの、つまりドキュメントのタイトルを変える、タイマーを開始する、ブラウザのイベントを購読するといったものは副作用です。useEffect はそのコードをレンダリングのに、表示をブロックせずに実行し、特定の値が変化したときに再同期します。

JSX
import { useEffect } from "react";

function CartTitle({ count }) {
  useEffect(() => {
    document.title = `Cart (${count})`;
  }, [count]);

  return <p>Items: {count}</p>;
}
この副作用は count が変化したときだけ再実行されます。

依存配列は副作用がどのくらいの頻度で実行されるかを制御します。[count]: count が変化したレンダリングのたびに副作用が再実行されます。[]: 副作用は最初のレンダリングの後に一度だけ実行されます。配列なし: 毎回のレンダリングの後に副作用が再実行されますが、これはめったに望ましくありません。黄金律: 副作用の中で使うすべてのコンポーネントの値は配列に登場させなければなりません。

JSX
import { useEffect, useState } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <p>Elapsed: {seconds} s</p>;
}
clearInterval がなければ、タイマーはコンポーネントより長く生き残ってしまいます。

副作用が返す関数がクリーンアップ関数です。React はそれを副作用が新たに実行される前に毎回呼び出し、コンポーネントがアンマウントされるときに最後にもう一度呼び出します。これがリークを防ぐものです。取り残されたタイマー、積み重なったリスナー、決してキャンセルされない購読などです。何かを「開く」副作用はすべて、それを再び閉じなければなりません。

JSX
useEffect(() => {
  function onResize() {
    setWidth(window.innerWidth);
  }
  window.addEventListener("resize", onResize);

  return () => {
    window.removeEventListener("resize", onResize);
  };
}, []);
resize イベントを購読し、アンマウント時にキャンセルします。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. useEffect(fn, []) の場合、関数 fn はいつ実行されますか?
    • コンポーネントのレンダリングのたびに、その前に
    • 最初のレンダリングの後に一度だけ
    • state が更新されるたびに
  2. 副作用が返すクリーンアップ関数はいつ呼び出されますか?
    • コンポーネントがアンマウントされたときだけ
    • 副作用が再実行される前と、アンマウント時に
    • コンポーネントの最初のレンダリングの直前に
    • 自動的には決して呼ばれない: 自分で呼び出さなければならない
  3. 副作用が userId を読み取るのに、その依存配列が [] です。何が問題ですか?
    • userId が変化しても副作用が再実行されず、古い値を持ち続ける
    • React が最初のレンダリングでエラーを投げる
    • 副作用が無限ループで再実行される