カテゴリ: React 更新日: 2026/02/09

Reactのライフサイクルにおけるアンチパターンまとめ!初心者でもわかるReactの注意点

ライフサイクルにおけるアンチパターンまとめ
ライフサイクルにおけるアンチパターンまとめ

先生と生徒の会話形式で理解しよう

生徒

「Reactでライフサイクルを使うときにやってはいけないことってありますか?」

先生

「はい、いくつか典型的なアンチパターンがあります。知らずにやるとバグの原因になったり、パフォーマンスが悪くなったりします。」

生徒

「例えばどんなことですか?」

先生

「例えば副作用の無限ループや、状態管理の誤用などですね。それぞれ詳しく見ていきましょう。」

1. useEffectやuseLayoutEffectでの無限ループ

1. useEffectやuseLayoutEffectでの無限ループ
1. useEffectやuseLayoutEffectでの無限ループ

Reactの副作用フックで依存配列を正しく設定しないと、レンダリングごとに副作用が繰り返されて無限ループになります。例えば、useEffect内で状態を更新し、その状態を依存配列に含めない場合です。


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

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

  useEffect(() => {
    setCount(count + 1); // 依存配列が空だと無限ループ
  }, []);

  return <p>{count}</p>;
}

export default LoopExample;
(useEffectで依存配列を正しく設定しないとレンダリングが止まらなくなります)

2. 状態の過剰更新

2. 状態の過剰更新
2. 状態の過剰更新

コンポーネント内で状態を頻繁に更新すると、レンダリングが何度も発生しパフォーマンスが低下します。特に大きなリストや複雑なUIでは顕著です。

対策としては、状態をまとめたり、useMemoやuseCallbackで関数や計算結果をキャッシュすることが有効です。

3. 副作用のタイミングを誤る

3. 副作用のタイミングを誤る
3. 副作用のタイミングを誤る

useEffectはレンダリング後に実行され、useLayoutEffectはレンダリング直後に実行されます。ここを誤解すると、画面表示が崩れたり、予期せぬ副作用が発生します。

例えばDOMのサイズを計算する場合はuseLayoutEffectを使い、データフェッチなどの非表示処理はuseEffectを使うのが基本です。

4. コンポーネントのアンマウント時の処理忘れ

4. コンポーネントのアンマウント時の処理忘れ
4. コンポーネントのアンマウント時の処理忘れ

コンポーネントがアンマウントされる際にタイマーやイベントリスナーを解除しないと、メモリリークやエラーの原因になります。useEffectのクリーンアップ関数で必ず解除しましょう。


import React, { useEffect } from "react";

function TimerExample() {
  useEffect(() => {
    const id = setInterval(() => console.log("tick"), 1000);
    return () => clearInterval(id); // クリーンアップを忘れない
  }, []);

  return <p>タイマー動作中</p>;
}

export default TimerExample;
(クリーンアップを忘れるとコンポーネントが削除されてもタイマーが動き続けます)

5. propsの直接操作や外部状態の誤用

5. propsの直接操作や外部状態の誤用
5. propsの直接操作や外部状態の誤用

親コンポーネントから渡されたpropsを直接変更することは避けましょう。Reactではpropsは読み取り専用です。また、外部のグローバル状態を直接変更するのもアンチパターンです。

状態を更新する場合は必ずsetStateやContextを通して変更するのが安全です。

6. レンダリングの副作用をUIに混ぜる

6. レンダリングの副作用をUIに混ぜる
6. レンダリングの副作用をUIに混ぜる

レンダリング中にコンソールログやDOM操作などの副作用を直接行うと、並行レンダリングや再レンダリング時に不整合が起こります。副作用はuseEffectやuseLayoutEffect内で行うようにしてください。

7. 重い処理を直接レンダリングに入れる

7. 重い処理を直接レンダリングに入れる
7. 重い処理を直接レンダリングに入れる

レンダリング内で重い計算やAPI呼び出しを行うと、画面がフリーズすることがあります。重い処理はuseMemoでメモ化したり、非同期で処理することが推奨されます。

8. イベントハンドラ内で状態の誤更新

8. イベントハンドラ内で状態の誤更新
8. イベントハンドラ内で状態の誤更新

ボタンや入力フォームのイベントで状態を連続更新する場合、以前の状態を正しく参照していないと期待通りに動かないことがあります。関数型更新(prevStateを使う)で正確に更新しましょう。

9. useEffectの依存配列の誤設定

9. useEffectの依存配列の誤設定
9. useEffectの依存配列の誤設定

依存配列を空にする、あるいは必要な値を含めないと副作用が正しく発火しません。常に必要な値を含めるようにして、Reactの警告も確認してください。

10. コンポーネント分割の不十分

10. コンポーネント分割の不十分
10. コンポーネント分割の不十分

1つのコンポーネントに多くの処理や状態を詰め込みすぎると、ライフサイクルの管理が複雑になり、アンチパターンにつながります。小さく分割して単純なコンポーネントにすることで、ライフサイクルを正しく管理できます。

まとめ

まとめ
まとめ

Reactの開発において、ライフサイクルの理解は避けて通れない非常に重要なテーマです。特に、モダンなReact開発の主流である「関数コンポーネント」と「Hooks(フック)」の組み合わせでは、直感的にコードを書いてしまうと、今回紹介したようなアンチパターンに陥りやすい傾向があります。

React開発者が意識すべきポイントの再確認

Reactのレンダリングは、私たちが想像するよりも頻繁に行われます。そのため、「いつ」「どのタイミングで」「何が」実行されるのかを正確に把握しておく必要があります。特に初心者が陥りやすいのが、useEffectの無限ループです。これは、副作用の中で自身の依存関係にある状態(ステート)を更新してしまうことで発生しますが、コードが複雑になればなるほど、どこでループが発生しているのか見つけにくくなります。

また、クリーンアップ関数の重要性も忘れてはいけません。タイマー処理(setInterval)や外部APIとの接続(WebSocket)、あるいはウィンドウのスクロールイベントの監視などを設定した際、コンポーネントが画面から消える(アンマウント)ときにそれらを解除しないと、ブラウザのメモリを無駄に消費し続け、最終的にはアプリケーション全体の動作が重くなる「メモリリーク」を引き起こします。

より実践的なReactコードの書き方

アンチパターンを回避し、より「Reactらしい」堅牢なコードを書くためには、以下のようなベストプラクティスを意識しましょう。例えば、状態を更新する際には「現在の値」に依存しないように、関数型の更新を利用することが推奨されます。


import React, { useState, useEffect, useCallback } from "react";

/**
 * ライフサイクルのベストプラクティスを取り入れたカウンターコンポーネント
 * 1. 関数型更新による状態管理
 * 2. 正確なクリーンアップ処理
 * 3. 不要な再生成を防ぐためのメモ化
 */
const SafeCounter = () => {
  const [count, setCount] = useState(0);
  const [isRunning, setIsRunning] = useState(true);

  // 1. 関数型更新を使うことで、countを依存配列に入れずに済む(無限ループ回避)
  const tick = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  useEffect(() => {
    let timerId;
    if (isRunning) {
      // 毎秒カウントアップするタイマーをセット
      timerId = setInterval(tick, 1000);
    }

    // 2. クリーンアップ関数でメモリリークを確実に防ぐ
    return () => {
      if (timerId) {
        clearInterval(timerId);
      }
    };
  }, [isRunning, tick]); // 必要な依存関係のみを指定

  return (
    <div className="p-4 border rounded shadow-sm bg-light">
      <h3 className="text-primary">実践的なタイマー</h3>
      <p className="fs-2">現在のカウント: {count}</p>
      <button 
        className="btn btn-warning me-2" 
        onClick={() => setIsRunning(!isRunning)}
      >
        {isRunning ? "一時停止" : "再開"}
      </button>
      <button 
        className="btn btn-danger" 
        onClick={() => setCount(0)}
      >
        リセット
      </button>
    </div>
  );
};

export default SafeCounter;
(画面には現在のカウントが表示され、1秒ごとに自動で増えていきます。停止ボタンを押すとカウントが止まり、再度押すと動き出します。リセットボタンで0に戻ります。コンポーネントが消える際にはタイマーも自動で破棄されるため安全です。)

これからの学習に向けて

Reactは常に進化を続けており、React 18以降の「並行レンダリング(Concurrent Rendering)」の導入により、副作用の扱いはさらに厳格な理解が求められるようになっています。初心者のうちは「動けばいい」と考えがちですが、中長期的なメンテナンス性を考えると、アンチパターンを避け、Reactのライフサイクルという「仕組み」に逆らわないコードを書くことが上達への近道です。

まずは小さなコンポーネントから、依存配列の漏れがないか、不要なレンダリングが発生していないかを確認する癖をつけていきましょう。ブラウザのデベロッパーツールや、React Developer Toolsを活用して、実際にコンポーネントがどのような挙動をしているか可視化するのも非常に効果的です。

先生と生徒の振り返り会話

生徒

「先生、今回のまとめでライフサイクルの重要性がよく分かりました!useEffectの中で安易にsetCount(状態更新)をしてしまうのが、なぜあんなに危険なのかがようやく腑に落ちました。」

先生

「それは良かったです。useEffectは強力ですが、その分『魔法』のように見えてしまうことがあります。自分が書いたコードが、どのタイミングで、何度実行されるのかを意識するのがプロへの第一歩ですよ。」

生徒

「特にクリーンアップ関数の話が印象的でした。今まで『消えるときの処理』なんてあまり考えていなかったので、タイマーが裏で動き続けるという話を聞いて、自分の過去のコードが怖くなりました(笑)」

先生

「ははは、誰でも最初はそうですよ。でも、メモリリークはユーザーのブラウザを重くする原因になりますから、これからは『セットしたら片付ける』という習慣を大切にしましょう。」

生徒

「はい!あと、依存配列(Dependency Array)の指定も。Reactの公式ドキュメントやリンター(ESLint)が警告を出してくれる理由が分かりました。あえて空にするのと、書くべきなのに書かないのは全然違うんですね。」

先生

「その通りです。Reactのルールを守ることは、結果的にバグの少ない快適なアプリケーションを作ることにつながります。次は、カスタムフックを使ってこれらのロジックをきれいに分離する方法についても学んでいきましょうか。」

生徒

「ぜひお願いします!もっと洗練されたReactのコードが書けるようになりたいです!」

カテゴリの一覧へ
新着記事
New1
React
Reactのフォーム入力を再利用しよう!初心者でもわかるフィールドコンポーネント化の考え方
New2
React
Reactでできること一覧!初心者でもわかるWebアプリ・スマホアプリ・PWAの活用方法
New3
React
React.jsが人気な理由!VueやAngularとの違いを初心者向けに徹底比較
New4
Next.js
Next.js×StorybookでUI開発環境を整える方法!初心者でもわかるNext.jsの環境構築
人気記事
No.1
Java&Spring記事人気No1
React
Reactでキーボードイベントを活用する方法!onKeyDown, onKeyUp, onKeyPressを初心者向けに解説
No.2
Java&Spring記事人気No2
React
ReactのPresentational Componentを完全ガイド!初心者でもわかるStateを持たないコンポーネントの特徴
No.3
Java&Spring記事人気No3
React
Reactでフォーカスイベントを制御する方法!onFocusとonBlurを初心者向けに解説
No.4
Java&Spring記事人気No4
React
ViteでReact開発環境を構築する手順を完全ガイド!初心者でもできるReactの環境構築
No.5
Java&Spring記事人気No5
React
Reactとは?初心者でもわかるReact.jsの基本概念と特徴をやさしく解説
No.6
Java&Spring記事人気No6
Next.js
Next.js Server Componentsのメリット・デメリットを完全解説!初心者でもわかるNext.jsの基本
No.7
Java&Spring記事人気No7
React
ReactとTypeScriptの環境構築をやさしく解説!Viteとtsconfigの設定も丁寧に紹介
No.8
Java&Spring記事人気No8
React
Reactのカードコンポーネントを汎用的に設計する方法!初心者でもわかる再利用の考え方