useEffectの「副作用」とは?初心者が純粋関数から理解したメモ
##【React】useEffectの「副作用」とは?初心者が純粋関数から理解したメモ
Reactを勉強していて useEffect を調べると必ず出てくる「副作用 (Side Effects)」という言葉。
私が最初に感じたのは「プログラミングで副作用とはなんぞや?そもそもuseEffectは必要なのか?」ということでした。
公式ドキュメントや技術ブログを読み、自分なりに「なぜuseEffectが必要なのか」が少し理解できたのでまとめます。
useEffectのことを考えるには純粋関数を把握しておかないといけません。
1. コンポーネントは「純粋」でなければならない
Reactのコンポーネントは、純粋関数 (Pure Function) であることが求められます。純粋関数とは、以下の特徴を持つ関数です。
純粋関数の条件
これには2つ条件があります。
- 同じ入力(Props, State)を与えれば、何度実行しても必ず同じ出力(JSX)が返ってくること。
- 関数の「外側」にある変数やシステムを変更しないこと。
純粋な関数 (Good)
入力 a,b だけで結果が決まり、外部には一切影響を与えません。
const calculate = (a: number, b: number): number => {
const result = a + b;
return result;
};
// calculate(1, 2) は何度実行しても必ず 3 になる
純粋ではない関数 (Bad)
関数の外にある total を書き換えています。実行するたびに外部の状態が変わってしまうため、予測不可能です。
let total = 0; // グローバル(外部)変数
const calculateImpure = (a: number): void => {
total += a; // 外の世界を書き換えている(副作用!)
};
2. 副作用 (Side Effects) とは?
1でまとめた純粋関数の原則から外れる「レンダリング(計算)以外の動作」を副作用 (Side Effects) と呼びます。
具体的な副作用の例
- 画面のDOMを直接書き換える
- DB(データベース)やAPIからデータを取得する
- ログを出力する、タイマーをセットする
これらは「UIを作るための計算」ではなく、「外部システムとの同期」や「状態の変更」にあたるため、レンダリング中のコンポーネントに直接書いてはいけません。
なぜ「DBからのデータ取得」は副作用なのか?
私が最初 useEffect を使用したのは「DBからデータを取得して表示するとき」でした。
なぜDBへのアクセスが「副作用(=純粋ではない)」扱いになるのでしょうか?
- 結果が保証されない: サーバーがダウンしていればエラーになるし、DBの中身が更新されれば返ってくるデータも変わります。「何度やっても同じ結果」にはなりません。
- 時間がかかる: 通信には時間がかかります。瞬時に値を返す「計算式」として振る舞えません。
3. 副作用をどこに書けばいいの?
コンポーネント本体(レンダリング中)に書けないとしたら、どこに書けばいいの?
Reactでは主に2つの場所が用意されています。
① イベントハンドラ (Event Handlers)
「ユーザーがボタンをクリックした」「フォームを送信した」といったタイミングで実行される関数です。
- タイミング: ユーザー操作時
- 純粋性のルール: 適用されません。
- 用途: データの送信 (POST) や、操作に伴う変更。
イベントハンドラはレンダリングの計算中には実行されないため、ここで副作用(API通信など)を行っても問題ありません。
② useEffect
ここで出てくるのが今回の本題、useEffect です。
これは「画面が表示された(マウントされた)」「データが更新された」というタイミングで実行されるフックです。
- タイミング: レンダリングが完了し、画面に反映された後
- 用途: 外部システムとの同期 (データの取得/GET、サブスクリプションの開始など)。
クリックなどのイベントではなく、コンポーネントのマウントや依存値の変更をきっかけに自動実行したい副作用は useEffect に記述します
4. useEffectを実際に使ってみる
これは私が自分のブログサイトを作っている際に実際に書いたコードです。
「ブログサイトを開いたら、最初からブログ記事一覧が表示されている」という状態にしたいため、クリックイベントではなく useEffect を使ってデータを取得しています。
useEffect(() => {
// useEffectの直下には async を書けないため、関数を作って呼ぶ
const loadBlogs = async () => {
try {
setIsLoading(true);
const blogList = await getAllBlogs();
// FirestoreBlogDataをBlogDataに変換
const convertedBlogs: BlogData[] = blogList.map(convertToBlogData);
setBlogs(convertedBlogs);
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : "ブログの取得に失敗しました";
setError(errorMessage);
console.error("ブログの取得に失敗しました:", err);
} finally {
setIsLoading(false);
}
};
loadBlogs();
}, []); // 依存配列が空 [] なので、コンポーネントマウント時に1回だけ実行される
Q. クリーンアップ関数は何のためにある?
useEffect を使う際、もう一つ重要なのが「クリーンアップ関数」です。
useEffect の中で return される関数は後始末に使われます。
useEffect(() => {
const timer = setInterval(...) // 同期開始
// ここで返した関数がクリーンアップとして実行される
return () => {
clearInterval(timer) // クリーンアップ(破棄)
}
}, [])
これは「同期を止める」処理ですが、「コンポーネントが不要になった時(画面遷移など)」や「次の副作用が始まる前」に、古い処理が残り続けないようにするためのものです。
これを行わないと、メモリリーク(メモリの食い潰し)や予期せぬバグにつながります。
まとめ
わからないなりに useEffect についてまとめてみました。
- Reactコンポーネントは純粋関数でなければならない(同じ入力=同じ出力)。
- 副作用(API通信、DOM操作など)はレンダリング計算の外に出さなければならない。
- ユーザー操作に基づく副作用はイベントハンドラに書く。
- 外部システムとの同期(初期データ取得など)は useEffect に書く。
Reactにおいて useEffect を使うのは、コンポーネントを「純粋」に保ちながら、必要な「外部との連携」を行うための仕組みだということが分かりました。
参考にした記事
【補足】
最近のReactでは、以下の方法が推奨されているようです。
サーバー側で取得する: Server Components を使い、useEffect を使わずにサーバー側で直接データを取得する。
またはライブラリを使う: SWR や TanStack Query などのデータ取得用ライブラリを使う(キャッシュ管理などが楽になるため)。
Discussion