目次
- そもそも「副作用」って何?
- useEffect の基本的な使い方
- 依存配列 (dependencies) の仕組み
- クリーンアップ関数 (cleanup)
- よくある使用例
- 注意点
- まとめ
そもそも「副作用」って何?
React の世界観では、コンポーネントの仕事は「入力(props・state)に基づいて UI を描画する」こととされています。しかしそれだけでは実用的なアプリを作れないので、コンポーネントの描画以外で必要になる処理、たとえば次のようなことを行わなければなりません。
- データの取得(API へのリクエスト)
- イベントリスナーの登録・解除
- setTimeout や setInterval でのタイマー処理
- ブラウザのタイトルや他の外部リソースを操作
- コンポーネントがアンマウントされる前にリソースを開放(例: WebSocket の切断など)
これらを React ではまとめて**「副作用 (Side Effect)」**と呼びます。コンポーネントの描画ロジックだけでは完結しない “外部とやり取りをする処理” と考えるればわかりやすい気がします。
useEffectの基本的な使い方
インポート方法
import React, { useEffect } from 'react';
シンプルな使用例
useEffect(() => {
// ここに副作用を引き起こす処理を書く
});
「コンポーネントがレンダリングされるたびにログを出す」という簡単な例を考えてみましょう。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// useEffect の基本的な書き方
useEffect(() => {
console.log("再レンダリングされました。count=", count);
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>クリック</button>
</div>
);
}
export default Example;
解説
ここでは依存配列なし(useEffect(() => {...}))で書いているため、初回描画 + state(count)更新などで再レンダリングが起こるたびに この console.log が実行されます。
依存配列 (dependencies) の仕組み
useEffect が呼ばれるタイミングは、第2引数に与える依存配列 によってコントロールできます。
useEffect(() => {
// 副作用の処理
}, [依存する変数たち]);
依存配列を使う理由
- 再レンダリングごとに毎回動いてほしくない とき
- 特定の値が変わったときだけ動いてほしい とき
- 初回だけ(マウント時だけ)動いてほしい とき
どのタイミングを調整するために、依存配列(deps array) を使います。
依存配列が空の場合: []
useEffect(() => {
// 1度だけ実行
}, []);
コンポーネントの初回レンダリング時だけ 実行される。
例: マウント時にデータ取得
useEffect(() => {
fetchData(); // コンポーネントが初回描画されるときにデータ取得
}, []);
特定の状態や props を指定した場合
useEffect(() => {
// この処理は count が変わったときだけ実行される
console.log("countが変わりました: ", count);
}, [count]);
- count が変わったときのみ、ここに書かれた関数が呼び出される
- 依存配列に変数が複数あれば、そのどれかが変化したときに実行される
例: 入力値が変わったときだけバリデーション
useEffect(() => {
checkValidation(inputValue);
}, [inputValue]);
クリーンアップ関数 (cleanup)
useEffect では、return でクリーンアップ関数を返す ことができます。
これは「コンポーネントがアンマウント(消える)するとき」や
「依存配列の値が変わるタイミングで再実行される前」に呼ばれます。
なぜ必要?
- イベントリスナーを登録した後に、コンポーネントが削除されたらリスナーを解除したい場合
- タイマー(setInterval)や WebSocket 通信など、リソースを解放する必要がある場合
useEffect(() => {
const handleScroll = () => {
console.log("スクロール中");
};
window.addEventListener("scroll", handleScroll);
// return でクリーンアップ関数を指定
return () => {
// コンポーネントがアンマウントされるタイミングなどで、リスナーを解除
window.removeEventListener("scroll", handleScroll);
};
}, []);
よくある使用例
データ取得(APIリクエスト)
最も一般的なのが、コンポーネントがマウントされたらデータを取得する というパターンです。
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
// マウント時にデータを取りに行く
fetch("https://example.com/api/users")
.then(response => response.json())
.then(data => {
setUsers(data);
})
.catch(error => {
console.error("エラー:", error);
});
}, []);
return (
<div>
<h3>ユーザーリスト</h3>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
解説
- useEffect(..., []) とすることで 初回1回だけ fetch が呼ばれ、データが取得される
- データ取得が終わったら setUsers で状態を更新し、リストを表示する
イベントリスナー(マウントとアンマウント)
上記で紹介したスクロールリスナーや、リサイズリスナーなど、イベントを登録してクリーンアップするパターンです。
useEffect(() => {
const handleResize = () => {
console.log("ウィンドウサイズが変わりました:", window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
注意点
- useEffect の中で状態を更新すると、再レンダリングが起きる
- その結果、再び useEffect が呼ばれる可能性があります
- 無限ループにならないよう、依存配列をしっかり設定するのが重要です
- 非同期処理中にコンポーネントがアンマウントされることがある
- 例えば fetch が終わる前にコンポーネントが切り替わると、setState が呼ばれても実行対象がない状態になることがあります
- その場合は中断処理やフラグ管理などで安全に扱う必要があります
- 依存配列を正しく書く
- useEffect 内で使っている変数や関数は依存配列に含める、というのが基本ルールです
- ESLint(eslint-plugin-react-hooks)を導入すると、抜けやミスを警告してくれます
- 依存配列を空にした場合、実行は「初回のみ」
- 途中で state が変わっても再実行されない点に注意です
- 何度も動かしたい処理なら、依存配列に状態変数を入れるか、あるいは依存配列を指定しない(毎回動く)ようにします
まとめ
- useEffect は React 関数コンポーネントで「副作用(side effect)」を扱うためのフック
- 副作用:データ取得、イベントリスナー、タイマー設定、DOM 操作 など
- useEffect(() => { 処理 }, [依存する値]) の形で使う
- 依存配列が空 ([]) ならマウント時のみ実行
- 依存配列を省略すると毎回実行
- 特定の値を配列に指定すると、その値が変化したときにのみ再実行
- クリーンアップ関数(return () => {...})を使うと、アンマウント時や再度 effect が実行される直前に処理を入れられる
- 正しい依存配列の設定やクリーンアップが重要