React useEffect:基本的な内容から「不要なパターン」

React useEffect:基本的な内容から「不要なパターン」

投稿日: 2025年11月12日

Tips
学習振り返り
要約
  • ReactのuseEffectは副作用処理を扱うためのフックであり、UIレンダリングとは異なる処理を実行するために使用される。
  • 不要なuseEffectの使用パターンとして、レンダリングのためのデータ変換やユーザーイベント処理が挙げられる。
  • 副作用を避けるためには、必要な計算をコンポーネントレンダリング中に直接行うことが推奨される。
音声で記事を再生

 【はじめに】🗿

この記事は、Reactの「useEffectとは何か?」
「副作用とは何か?」という基本的な内容から、「不要なパターン」までを紹介していきます!

【useEffectとは何か?】🗿🗿

useEffectは、コンポーネントの中で「副作用」と呼ばれる処理を実行するためのフックです!!

【副作用とは?】🗿🗿🗿

副作用とは、コンポーネントの主な役割であるUI表示(レンダリング)とは異なる、例えば以下のようなものを指す。

  • サーバーからのデータ取得(fetch、API通信)

  • チャットサーバーに接続する

  • タイマー(setTimeout、 setInterval)の設定

  • DOM(HTML要素)を直接操作する処理(マップ表示、 スクロール表示、 モーダル表示)

これらの処理を、コンポーネントの表示タイミングに合わせて実行・管理するのがuseEffectの役割です😎

【useEffectで不要なパターン】🗿🗿🗿🗿

useEffectで不要なパターンは一般的に次のケースとなります。

  • レンダリングのためのデータ変換時のエフェクト(propsやstateから計算できる値)

  • ユーザーイベント処理のエフェクト(ボタンクリック時のイベントハンドラの直接処理)

1. フィルター処理(レンダリングのためのデータ変換)

❌ 悪い例(useEffectとuseStateでフィルター管理)

"use client";

// 子コンポーネント

import { useState, useEffect } from 'react';

type FilteredListProps = {
    list: {  id: number; text: string; category: string}[];
    filter: string;
}

export const FilteredListComponent = ({ list, filter }: FilteredListProps) => {
  // フィルター結果を別途stateで管理している
  const [filteredList, setFilteredList] = useState(list);

  useEffect(() => {
    // 4. useEffect が実行される
    console.log('❌ useEffectが実行されました');
    const result = list.filter(item => {
      if (filter === 'all') return true;
      return item.category === filter;
    });
    // 5. setFilteredList(...) が呼ばれ、state が更新される
    setFilteredList(result);
  }, [list, filter]);

  // 2. 1回目のレンダリング (props変更後)
  // 6. 2回目のレンダリング (setFilteredList後)
  console.log('❌ コンポーネントがレンダリングされました');

  return (
    <ul>
      {filteredList.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

"use client";


 // 親コンポーネント
import { useState } from "react";
import { FilteredListComponent } from "./component/FilteredListComponent";

const Home = () => {
  // ダミーデータ(元のリスト)
  const initialList = [
    { id: 1, text: "アイテムA", category: "X" },
    { id: 2, text: "アイテムB", category: "Y" },
    { id: 3, text: "アイテムC", category: "X" },
  ];

  const [filter, setFilter] = useState("all");
  return (
    <div>
      {/* 1. props が変わる */}
      <button onClick={() => setFilter("all")}>すべて</button>
      <button onClick={() => setFilter("X")}>カテゴリX</button>
      <FilteredListComponent list={initialList} filter={filter} />
    </div>
  );
};

export default Home;
  1. 元のリスト(propsやstate)でフィルター管理

  2. コンポーネントが1回目のレンダリングを(古いフィルター結果で)実行する。

  3. 描画が更新される

  4. useEffectが実行される

  5. useEffect内でsetFilterList(…)が呼ばれ、stateが更新される

  6. stateが更新されるため、コンポーネントが2回目のレンダリングを(新しいフィルター結果で)実行される

また、このような書き方をすると
useEffectの中でstateを直接更新すると、連続したレンダリングが起きてパフォーマンスが悪くなるから、やめたほうがいいですよ」という警告のエラーが出ます!!

⭕️ 良い例(レンダリング中に計算)

効率的な流れ(props変更時)

"use client";

// 子コンポーネント

type FilteredListProps = {
    list: {  id: number; text: string; category: string}[];
    filter: string;
}

export const FilteredListComponent = ({ list, filter }: FilteredListProps) => {
    // 2. 1回目のレンダリングが実行される
  console.log('⭕ コンポーネントがレンダリングされました');

  // 3. レンダリングの最中に、計算が実行される
  const filteredList = list.filter(item => {
    console.log('⭕ フィルター処理が実行されました');
    if (filter === 'all') return true;
    return item.category === filter;
  });

  // 4. 計算結果を使って描画される
  return (
    <ul>
      {filteredList.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

"use client";

// 親コンポーネント
import { useState } from "react";
import { FilteredListComponent } from "./component/FilteredListComponent";

const Home = () => {
  // ダミーデータ(元のリスト)
  const initialList = [
    { id: 1, text: "アイテムA", category: "X" },
    { id: 2, text: "アイテムB", category: "Y" },
    { id: 3, text: "アイテムC", category: "X" },
  ];

  const [filter, setFilter] = useState("all");
  return (
    <div>
      {/* 1. props が変わる */}
      <button onClick={() => setFilter("all")}>すべて</button>
      <button onClick={() => setFilter("X")}>カテゴリX</button>
      <FilteredListComponent list={initialList} filter={filter} />
    </div>
  );
};

export default Home;
  1. (親から渡される)listまたはfilterのpropsが変わる。

  2. コンポーネントが1回目のレンダリング実行する。

  3. そのレンダリングの最中に、const filteredList = list.filter(…)の計算が実行される。

  4. 計算された最新のfilteredListを使って、画面の描画が更新される。

👍結論:

  • 良い例では、レンダリングが1回で完了します。useEffectを使うと、常に2回のレンダリングが発生してしまい、非常に非効率。

  • 既存のpropsやstateから計算できるものは、stateに入れないのがポイントです!

  • listとfilterという「情報の源」(propsやstate)さえあれば計算できる値を、レンダリング中に直接計算する。

  • こういう重たい計算・配列処理の場合は、useMemoでラップするのが良いかもしれないです!

2. イベントハンドラー処理

❌ 悪い例(useEffectでイベントを処理)

ボタンが押されたという「事実」をstate(shouldShowAlert)に記録し、useEffectでそのstateを監視して処理を実行する、遠回りなパターン。

import { useState, useEffect } from 'react';

// 子コンポーネント

function AlertButton() {
  // ❌ 「アラートを出すべきか」というイベント(操作)をstateで管理
  const [shouldShowAlert, setShouldShowAlert] = useState(false);

  // ❌ state(shouldShowAlert)の変化を監視して処理を実行
  useEffect(() => {
    // 5. useEffect が shouldShowAlert の変更を検知して実行される
    if (shouldShowAlert) {
      // 6. alert が実行される
      alert('ボタンが押されました!');
      
      // 7. state が再度更新される
      setShouldShowAlert(false);
    }
  }, [shouldShowAlert]); // stateに依存

  function handleClick() {
    // 2. setShouldShowAlert(true) が呼ばれ、state が更新される
    setShouldShowAlert(true);
  }

  // 3. 1回目の再レンダリング
  // 8. 2回目の再レンダリング
  return (
    // 1. ボタンがクリックされる
    <button onClick={handleClick}>
      アラート表示 (悪い例)
    </button>
  );
}
  1. ユーザーが「アラート表示 (悪い例)」ボタンをクリック。

  2. onClick ハンドラが setShouldShowAlert(true) を呼び出し、shouldShowAlert の state が更新。

  3. state が更新されたため、コンポーネントが1回目の再レンダリングを実行。

  4. 画面の描画が更新。

  5. useEffect が shouldShowAlert の変更(trueになったこと)を検知して実行。

  6. useEffect 内で alert(...) が実行。

  7. (さらに)setShouldShowAlert(false) が呼ばれ、shouldShowAlert の state が再度更新。

  8. state が再度更新されたため、コンポーネントが2回目の再レンダリングを実行。

フィルター処理の時と同様に、
「連続したレンダリングが起きてパフォーマンスが悪くなるから、やめたほうがいいですよ」という警告のエラーが出ます!!

⭕ 良い例(イベントハンドラで直接処理

ボタンがクリックされたら、onClickに渡したハンドラ(handleClick)が直接処理を実行する、最も標準的で効率的なパターン。

"use client";

// 子コンポーネント

export const AlertButton = () => {
  // イベントハンドラ内で直接ロジックを実行
  function handleClick() {
    // 2. alertを直接実行
    alert("ボタンが押されました!");
  }

  return (
    // 1. ボタンがクリックされる
    <button onClick={handleClick}>アラート表示 (良い例)</button>
  );
};

"use client";

// 親コンポーネント

import { useState } from "react";
import { FilteredListComponent } from "./component/FilteredListComponent";
import { AlertButton } from "./component/Button/AlertButton";
const Home = () => {
  // ダミーデータ(元のリスト)
  const initialList = [
    { id: 1, text: "アイテムA", category: "X" },
    { id: 2, text: "アイテムB", category: "Y" },
    { id: 3, text: "アイテムC", category: "X" },
  ];

  const [filter, setFilter] = useState("all");
  return (
    <div>
      {/* 1. props が変わる */}
      <button onClick={() => setFilter("all")}>すべて</button>
      <button onClick={() => setFilter("X")}>カテゴリX</button>
      <FilteredListComponent list={initialList} filter={filter} />
      <AlertButton />
    </div>
  );
};

export default Home;
  1. ユーザーが「アラート表示 (良い例)」ボタンをクリック。

  2. onClick ハンドラが直接 alert(...) を実行。

👍 結論: 

クリック(イベント)と、実行したい処理(アラート)が直結しています。
余計なstateもuseEffectも不要で、不要な再レンダリングは発生しません。

以下、参考資料


【おわり】🗿🗿🗿🗿🗿

useEffectによる、「基本的な内容」から「不要なパターン」を紹介いたしました。

useEffectあまり使わないと思いますが、理解が曖昧で参考になる人がいれば幸いです笑

シェア!

XThreads
Loading...
記事一覧に戻る
XThreads
0