React useEffect:基本的な内容から「不要なパターン」
投稿日: 2025年11月12日
この記事は、Reactの「useEffectとは何か?」
「副作用とは何か?」という基本的な内容から、「不要なパターン」までを紹介していきます!
useEffectは、コンポーネントの中で「副作用」と呼ばれる処理を実行するためのフックです!!
副作用とは、コンポーネントの主な役割であるUI表示(レンダリング)とは異なる、例えば以下のようなものを指す。
サーバーからのデータ取得(fetch、API通信)
チャットサーバーに接続する
タイマー(setTimeout、 setInterval)の設定
DOM(HTML要素)を直接操作する処理(マップ表示、 スクロール表示、 モーダル表示)
これらの処理を、コンポーネントの表示タイミングに合わせて実行・管理するのがuseEffectの役割です😎
useEffectで不要なパターンは一般的に次のケースとなります。
レンダリングのためのデータ変換時のエフェクト(propsやstateから計算できる値)
ユーザーイベント処理のエフェクト(ボタンクリック時のイベントハンドラの直接処理)
"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;元のリスト(propsやstate)でフィルター管理
コンポーネントが1回目のレンダリングを(古いフィルター結果で)実行する。
描画が更新される
useEffectが実行される
useEffect内でsetFilterList(…)が呼ばれ、stateが更新される
stateが更新されるため、コンポーネントが2回目のレンダリングを(新しいフィルター結果で)実行される
また、このような書き方をすると
「useEffectの中でstateを直接更新すると、連続したレンダリングが起きてパフォーマンスが悪くなるから、やめたほうがいいですよ」という警告のエラーが出ます!!
"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;(親から渡される)listまたはfilterのpropsが変わる。
コンポーネントが1回目のレンダリング実行する。
そのレンダリングの最中に、const filteredList = list.filter(…)の計算が実行される。
計算された最新のfilteredListを使って、画面の描画が更新される。
👍結論:
良い例では、レンダリングが1回で完了します。useEffectを使うと、常に2回のレンダリングが発生してしまい、非常に非効率。
既存のpropsやstateから計算できるものは、stateに入れないのがポイントです!
listとfilterという「情報の源」(propsやstate)さえあれば計算できる値を、レンダリング中に直接計算する。
こういう重たい計算・配列処理の場合は、useMemoでラップするのが良いかもしれないです!
ボタンが押されたという「事実」を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>
);
}ユーザーが「アラート表示 (悪い例)」ボタンをクリック。
onClick ハンドラが setShouldShowAlert(true) を呼び出し、shouldShowAlert の state が更新。
state が更新されたため、コンポーネントが1回目の再レンダリングを実行。
画面の描画が更新。
useEffect が shouldShowAlert の変更(trueになったこと)を検知して実行。
useEffect 内で alert(...) が実行。
(さらに)setShouldShowAlert(false) が呼ばれ、shouldShowAlert の state が再度更新。
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;ユーザーが「アラート表示 (良い例)」ボタンをクリック。
onClick ハンドラが直接 alert(...) を実行。
👍 結論:
クリック(イベント)と、実行したい処理(アラート)が直結しています。
余計なstateもuseEffectも不要で、不要な再レンダリングは発生しません。
以下、参考資料
useEffectによる、「基本的な内容」から「不要なパターン」を紹介いたしました。
useEffectあまり使わないと思いますが、理解が曖昧で参考になる人がいれば幸いです笑