0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReactのuseEffectについて

Posted at

ReactのuseEffectは、副作用を扱うためのフックです。
「副作用」とは、データの取得・DOMの操作・イベントリスナーの登録・タイマーの設定など、Reactのレンダリングの外で起こる処理のことです。
ちなみに、「Reactのレンダリングの外」とは、コンポーネントが画面に表示される過程(=レンダリング)とは直接関係のない処理のことです。

基本的な使い方

useEffect(() => {
  // 副作用の処理(例:APIリクエスト、console.log など)
}, []);

引数の意味

useEffect(() => {
  // ① 副作用の処理を書く関数(必須)

  return () => {
    // ② クリーンアップ関数(任意)
  };
}, [依存配列]);
  • 第1引数:副作用の関数

  • 第2引数(依存配列)

    • 空配列 []初回マウント時のみ実行
    • 配列なし → レンダリングのたびに実行
    • 配列に値あり → その値が変化したときに実行

典型的なユースケース

① 初回マウント時のみ実行(初期化処理)

例:ページ読み込み時にデータを取得

useEffect(() => {
  fetch("/api/user").then((res) => res.json()).then(setUser);
}, []); // 空配列 → 初回だけ実行

使い道:初回だけ実行したい処理(API呼び出し・初期設定)

② 値の変化に応じて実行(依存配列あり)

例:選択したIDに応じてデータ再取得

useEffect(() => {
  fetch(`/api/user/${userId}`).then((res) => res.json()).then(setUser);
}, [userId]); // userId が変わるたびに実行

使い道:フォーム入力や選択値に応じた処理、フィルターの変更など

③ タイマー処理

例:5秒後にフラグを更新

useEffect(() => {
  const timer = setTimeout(() => {
    setIsVisible(false);
  }, 5000);

  return () => clearTimeout(timer); // クリーンアップ
}, []);

使い道:トースト通知の消去、遅延処理、非同期処理のシミュレートなど

④ フォーム入力のリアルタイムバリデーション

例:メールアドレスのバリデーションチェック

useEffect(() => {
  if (email.includes("@")) {
    setEmailValid(true);
  } else {
    setEmailValid(false);
  }
}, [email]);

使い道:入力補助やボタンの活性化制御など

⑤ ローカルストレージとの連携

例:ブラウザの設定を維持

useEffect(() => {
  const saved = localStorage.getItem("theme");
  if (saved) {
    setTheme(saved);
  }
}, []);

useEffect(() => {
  localStorage.setItem("theme", theme);
}, [theme]);

使い道:ダークモードやユーザー設定の保存・復元


よくあるミスや注意点

① 依存配列(第二引数)を省略する

useEffect(() => {
  fetch("/api/data").then(setData);
}); //  依存配列なし

問題点:レンダリングごとに毎回実行されて、無限ループや通信のしすぎの原因に

正しくは:

useEffect(() => {
  fetch("/api/data").then(setData);
}, []); // 初回のみ実行

② 不適切な依存配列(必要な値を入れ忘れる)

useEffect(() => {
  console.log(user.name); // userが変わっても再実行されない
}, []); //  userが依存配列にない

問題点:

  • 値は変わっているのに副作用が実行されない
  • 古い値を使ってしまう

正しくは:

useEffect(() => {
  console.log(user.name);
}, [user]); // userが変わったら再実行

③ 非同期関数を直接使う

useEffect(async () => {
  const res = await fetch("/api");
  const data = await res.json();
  setData(data);
}, []); // useEffectにasyncは直接書けない

問題点: useEffectasync を返すとエラーになる
useEffect の返り値は、クリーンアップ関数(または undefined)でなければならないです。

正しくは:

useEffect(() => {
  const fetchData = async () => {
    const res = await fetch("/api");
    const data = await res.json();
    setData(data);
  };
  fetchData();
}, []);

④ クリーンアップを忘れる

useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []); // クリーンアップがない

問題点:

  • イベントがどんどん登録されて「ゴーストイベント」になる
  • メモリリークの原因にもなる

正しくは:

useEffect(() => {
  window.addEventListener("resize", handleResize);
  return () => {
    window.removeEventListener("resize", handleResize); // ✅ クリーンアップ
  };
}, []);

⑤ 状態を連続で更新 → 無限ループ

useEffect(() => {
  setCount(count + 1); // useEffectで状態を変えると再実行され続ける
}, [count]);

問題点:

  • count を変えるたびに useEffect が実行される
  • setCount → countが変わる → useEffect再実行 → 無限ループ

対策:

  • 条件をつける、または setTimeout などで制御する
  • useRef を使って初回だけスキップするなど

useEffectを使わない方がよいケース

1. 状態に応じた処理が「そのままレンダリングに使える」とき

NG例(useEffectでバリデーションをしている)

const [email, setEmail] = useState("");
const [isValid, setIsValid] = useState(false);

useEffect(() => {
  setIsValid(email.includes("@"));
}, [email]);

よりシンプルな書き方(useEffect不要)

const isValid = email.includes("@");

状態 email から導かれる値 isValid は、
計算で求まるので useEffect ではなく「変数」または useMemo で十分です。

2. 状態を更新するだけのロジックをuseEffectに書いてしまう

NG例(ボタンを押したらカウントを上げる → useEffectで状態更新)

const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);

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

より適切な書き方(クリック時に直接更新)

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

const handleClick = () => {
  setCount((prev) => prev + 1);
};

「ボタンを押したら実行」→ これはイベントハンドラで処理すべきで、副作用ではない。

3. ユーザー入力の状態をリアルタイムに保持するだけのとき

NG例(入力を状態に反映するだけなのに useEffect)

const [input, setInput] = useState("");
const [text, setText] = useState("");

useEffect(() => {
  setText(input);
}, [input]);

正しくは text 状態そのものが不要

const [text, setText] = useState(""); // 入力と表示が同じならこれ1つでOK

「ある状態が別の状態にコピーされるだけ」なら そもそも状態を分けない方が良い

4. 他のReactフックで代替できるとき

やりたいこと 適した手法 なぜ useEffect は不要?
計算コストの高い処理の結果を保持 useMemo レンダリング時に再計算されない
イベント内の関数を再利用 useCallback 無駄な再生成を防げる
状態管理が複雑 useReducer 状態とロジックの分離ができる

無駄なuseEffectが増えることのデメリット

  • 可読性・保守性の低下
  • テストやデバッグが困難
  • 責務の分離ができなくなる
  • パフォーマンス低下

seEffectを適切に使うためには

  • JSXだけでできることはuseEffectに書かない
  • 状態の変化によって「Reactの外側」で何かするならuseEffect
  • 副作用は開始と終了をワンセットで考える
  • 「これuseEffect必要?」と1回立ち止まって考える

まとめ

useEffect は便利に見えるが「とりあえず書く」のではなく、使うべきタイミングと使わなくていい場面を見極めることが大切。
Reactの「状態の変化で自動的に再描画される」という特徴を活かしながら、useEffect最小限・適切に使えるようになれば、よりシンプルでバグの少ない設計が実現できる。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?