React Hooks について本ブログでもよく詳しい解説をしていますが、今回はその中でも特に重要な useEffect を解説します。
useState と同じぐらいよく使われる Hooks ですので、この機会にしっかりおさえておきましょう
ReactのuseEffectとは何か?
ReactのuseEffectは、関数型コンポーネントで副作用を扱うためのHookです。
副作用とは、関数の実行によって発生する外部の変更のことを指します。
例えば、APIからデータを取得する処理や、DOMの変更などが副作用にあたります。
useEffectは、コンポーネントがレンダリングされた後に実行されます。
これは、コンポーネントが画面に表示された後に何かを行いたいときに便利です。
例えば、以下のコードでは、コンポーネントがレンダリングされた後にこんにちは、世界!をコンソールに出力します。
useEffect(() => {
console.log('こんにちは、世界!');
}, []);
この例だけではuseEffectのメリットが分かりにくいかもしれません。
そこで、APIからデータを取得するより具体的な例を見てみましょう。
useEffect(() => {
fetch('https://api.example.com/items')
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
}, []);
上記の例では、useEffectを使用してAPIからデータを取得しています。
第2引数の空配列により、この副作用はコンポーネントがマウントされた時に一度だけ実行されます。
このように、useEffectを使うことで、クラスコンポーネントのライフサイクルメソッドを関数コンポーネントで扱うことができます。
ライフサイクルメソッドとは、コンポーネントの生成から破棄までの間に発生するイベントのことです。
例えば、コンポーネントが生成された時に実行されるcomponentDidMountや、コンポーネントが破棄される時に実行されるcomponentWillUnmountなどがあります。
useEffectの基本的な使い方
useEffectは、コンポーネントの初回レンダリング後と、依存配列(後述)にある何かが変更された時に実行されます。
以下の例では、countが変更されるたびに、その新しい値がコンソールに出力されます。
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`カウントは現在 ${count} です`);
}, [count]);
依存配列とは何か?
useEffectの第二引数として「依存配列」を提供します。
この配列は、useEffectが監視する値のリストです。
これらの値のうちの1つが変わるたびに、useEffect内のコードが再度実行されます。
ただし、依存配列が空の場合、useEffectはコンポーネントがマウントされた時に一度だけ実行されます。
そのため、画面が表示された時に一度だけ実行したい処理を記述する場合は、依存配列を空にすると良いでしょう。
useEffect(() => {
// 画面が表示された時に一度だけ実行したい処理
console.log('こんにちは、世界!');
}, []);
useEffectで非同期処理を行う
useEffectは、データのフェッチなどの非同期操作を行うときによく使われます。
ただし、useEffectのコールバック自体は同期的であるため、非同期関数を直接呼び出すことはできません。
そのため、通常は非同期関数を定義してからそれを呼び出します。
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
};
fetchData();
}, []);
クリーンアップ関数とは何か?
useEffectの内部から関数を返すことで、副作用の「クリーンアップ」を行うことができます。
このクリーンアップは、次の副作用が開始する前またはコンポーネントがアンマウントされるときに実行されます。
以下のコードでは、タイマーを設定し、そのタイマーをクリーンアップするための関数を返しています。
useEffect(() => {
const timerID = setTimeout(() => {
console.log('1秒経過しました');
}, 1000);
return () => {
clearTimeout(timerID);
};
}, []);
上記の例では、タイマーをクリーンアップするためにclearTimeoutを使用しています。
これがないと、コンポーネントがアンマウントされた後もタイマーが動き続けてしまい、思わぬバグの原因になります。
他の例も見てみましょう。
例えば、APIからデータを取得する処理を行う際に、その処理が終わる前にユーザーがページを離れてしまった場合、APIへのリクエストをキャンセルするために使うことができます。
useEffect(()
=> {
const controller = new AbortController();
const signal = controller.signal;
fetch('https://api.example.com/items', { signal })
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
return () => {
controller.abort();
};
}, []);
上記の例では、AbortControllerを使用してAPIへのリクエストをキャンセルしています。
これにより、ユーザーがページを離れた後もAPIへのリクエストが続くことを防ぐことができます。
useEffectの注意点
ループや条件文内での使用を避ける
ReactのHookはトップレベルで呼び出す必要があります。
ループ、条件、入れ子になった関数の中でHookを呼び出すことは避けましょう。
例えば、以下のようなコードは動作しません。
function Counter() {
const [count, setCount] = useState(0);
if (count === 0) {
useEffect(() => {
console.log('カウントは現在 0 です');
});
}
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増加</button>
</div>
);
}
上記の例では、countが0の時にのみuseEffectを呼び出そうとしていますが、React Hooks の仕様上、これは動作しません。
依存配列に関数やオブジェクトを含める際の注意
ReactのuseEffectを使う際、依存配列に関数やオブジェクトを含めることがあります。しかし、これには注意が必要です。なぜなら、関数やオブジェクトは、コンポーネントが再レンダリングされるたびに「新しい参照」を持つからです。
「新しい参照」とは何かというと、JavaScriptでは関数やオブジェクトはそれぞれ一意の場所(参照)に保存されます。そして、コンポーネントが再レンダリングされると、その中で定義された関数やオブジェクトは新たな場所に保存され、それが「新しい参照」になります。
これが問題となるのは、useEffectの依存配列に関数やオブジェクトを含めた場合です。依存配列に含まれる値が変更されると、useEffect内のコードが再実行されます。しかし、関数やオブジェクトが新しい参照を持つたびに、それが「変更」とみなされ、useEffectが再実行されてしまいます。
例えば、以下のコードを見てみましょう。
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
useEffect(() => {
console.log('カウント:' + count);
}, [increment]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増加</button>
</div>
);
}
このコードでは、increment関数がuseEffectの依存配列に含まれています。しかし、ユーザーが「増加」ボタンをクリックしてcountが増えるたびに、increment関数は新しい参照を持ちます。その結果、useEffect内のconsole.logが毎回実行されてしまいます。
これは、console.logが頻繁に実行されるだけでなく、もしuseEffect内で重い処理(例えば、APIからのデータ取得)を行っていた場合、パフォーマンスに影響を及ぼす可能性があります。
この問題を解決するためには、useCallbackを使ってincrement関数をメモ化します。これにより、countが変更されない限り、increment関数の参照は変わらず、useEffectは意図したタイミングでのみ実行されます。
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
useEffect(() => {
console.log('カウント:' + count);
}, [increment]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増加</button>
</div>
);
}
このように、useEffectの依存配列に関数やオブジェクトを含める際は、その影響を理解し、必要に応じてuseCallbackを使って関数をメモ化することが重要です。
コスパよく React を学習できる Udemy 講座人気ランキング
ちなみに姉妹サイトの Learning Next では、React を独学で学べる Udemy 講座の人気ランキングや、各講座の受講生レビューをもとにした分析スコアを公開しています。
「学習にあまりお金をかけたくない…」「スクールに通う時間がない」という方は、こちらを参考に Udemy 講座の学習も検討してはいかがでしょうか?
▶️ UdemyでReactを学べる講座の人気ランキング – Learning Next
ひとめで良い点・悪い点、さらにおすすめポイントが分かりますので、講座選びで失敗したくない方はぜひ活用してみてください!

