ReactのuseEffectでメモリリークを防ぐ方法!初心者でもわかるクリーンアップ関数の使い方
生徒
「先生、Reactのプログラムを作っていたら、エラーで『メモリリーク』って出たんですけど、これはなんですか?」
先生
「メモリリークというのは、もう使わないはずのメモリ(パソコンの作業スペース)が解放されずに残り続けてしまうことです。放っておくとパソコンが重くなったり、アプリが動かなくなったりするんです。」
生徒
「Reactでもそんなことが起きるんですか?」
先生
「はい。特にuseEffectを使って非同期処理やタイマーを設定したときに、クリーンアップを忘れるとメモリリークの原因になります。」
生徒
「じゃあどうやって防げばいいんですか?」
先生
「それにはクリーンアップ関数を使います。具体的にコードを見ていきましょう。」
1. メモリリークとは何か
メモリリークとは、アプリが使わなくなったデータや処理を解放しないで残してしまうことです。たとえるなら、使い終わったペットボトルを捨てずに机に溜め続けているようなものです。最初は気にならなくても、だんだん机がいっぱいになって仕事ができなくなります。パソコンも同じで、メモリリークが溜まると動作が重くなり、最悪の場合アプリが落ちることもあります。
2. useEffectでメモリリークが起きる原因
useEffectは、Reactで副作用(データの取得やタイマー処理など)を扱うときに使うフックです。しかし、コンポーネントが画面から消えたあとでも処理が残り続けると、メモリが解放されずにリークが起きます。
- API通信が終わる前に画面が切り替わる
- タイマーやイベントリスナーを設定したまま放置する
- コンポーネントがアンマウント(削除)されたのに処理が残っている
これらが原因で「Warning: Can't perform a React state update on an unmounted component」といったエラーが出ることがあります。
3. クリーンアップ関数とは?
クリーンアップ関数とは、コンポーネントが画面から消えるときに呼ばれる「お片付け用の関数」です。例えば学校の授業が終わったら黒板を消すように、使い終わった処理をきれいに消す役割があります。ReactのuseEffectでは、関数の中からreturn () => { ... }の形で書くことでクリーンアップを行います。
4. setIntervalをクリーンアップする例
よくある例が、setIntervalで時間ごとに処理を実行するケースです。これをそのままにすると、コンポーネントが消えてもタイマーが動き続けてメモリリークになります。
import React, { useState, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
return () => {
clearInterval(id);
};
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
export default Timer;
5. API通信でのクリーンアップ例
API通信でも同じ問題が起きます。例えばコンポーネントが消えたあとにsetStateを呼ぶとエラーになります。このときは、フラグを使って「もう処理しない」と判断させます。
import React, { useState, useEffect } from "react";
function FetchData() {
const [data, setData] = useState(null);
useEffect(() => {
let isMounted = true;
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((res) => res.json())
.then((result) => {
if (isMounted) {
setData(result);
}
});
return () => {
isMounted = false;
};
}, []);
return (
<div>
<h1>{data ? data.title : "読み込み中..."}</h1>
</div>
);
}
export default FetchData;
6. イベントリスナーのクリーンアップ
もうひとつよくあるのが、ブラウザのイベントリスナーです。例えばスクロールやリサイズイベントを設定すると、消すのを忘れがちです。これもremoveEventListenerでクリーンアップしましょう。
useEffect(() => {
const handleResize = () => {
console.log("画面サイズが変わりました");
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
こうしておけば、コンポーネントが消えたときに自動でリスナーが解除され、メモリリークを防げます。
7. クリーンアップを習慣にしよう
クリーンアップ関数は、Reactで安全にアプリを作るための基本です。最初は「お片付けを忘れないようにする」というイメージで大丈夫です。処理を追加したら「消す処理も書く」とセットで覚えると安心です。
特に初心者がつまずきやすいのが「エラーは出ていないけど、裏で処理が動き続けている」ケースです。こうした問題は一見気づきにくいですが、クリーンアップをきちんと書いておけば防げます。
まとめ
useEffectとメモリリークの関係を振り返る
今回の記事では、ReactのuseEffectを使う際に起こりやすいメモリリークの問題と、
それを防ぐためのクリーンアップ関数の使い方について詳しく学んできました。
Reactは便利なライブラリですが、非同期処理やタイマー、イベントリスナーを扱う場面では、
「使い終わった処理をきちんと片付ける」という意識を持たないと、
知らないうちにメモリリークが発生してしまいます。
メモリリークとは、もう必要のない処理やデータがメモリ上に残り続けてしまう状態です。
小さなサンプルでは気づきにくいですが、画面遷移が多いアプリや、
長時間動き続けるWebアプリでは、動作が重くなったり、
警告メッセージが表示されたりと、さまざまな不具合の原因になります。
そのため、ReactでuseEffectを書くときは、
「この処理はいつ始まり、いつ終わるのか」を常に考えることが重要です。
クリーンアップ関数の役割と考え方
クリーンアップ関数は、コンポーネントが画面から消えるタイミングで実行される、
いわば「後片付け専用の処理」です。
useEffectの中でreturn () => { ... }と書くことで定義でき、
タイマーの停止、イベントリスナーの解除、フラグの切り替えなどをまとめて行えます。
初心者の方は、「何かを始めたら、必ず終わらせる処理を書く」というルールを
ひとつの習慣として覚えておくと安心です。
特に、setIntervalやaddEventListener、
API通信後のsetStateなどは、
クリーンアップを忘れるとメモリリークにつながりやすい代表例です。
記事内で紹介したように、clearIntervalや
removeEventListenerを正しく呼び出すだけで、
多くのトラブルを未然に防ぐことができます。
シンプルな復習用サンプル
ここで、今回学んだ内容をまとめて確認できるシンプルな例を見てみましょう。 タイマーを使い、クリーンアップ関数で確実に停止することで、 メモリリークを防ぐ基本的な形になっています。
import React, { useState, useEffect } from "react";
function CleanupSummary() {
const [count, setCount] = useState(0);
useEffect(() => {
const timerId = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
return () => {
clearInterval(timerId);
};
}, []);
return (
<div>
<p>カウント: {count}</p>
</div>
);
}
export default CleanupSummary;
このように、クリーンアップ関数を正しく書いておけば、 コンポーネントがアンマウントされたあとに処理が残ることはありません。 見た目は小さな違いでも、実務ではアプリの安定性に大きく影響します。 Reactで安全に非同期処理を扱うための基礎として、 クリーンアップの考え方は必ず身につけておきたいポイントです。
生徒
「useEffectって便利ですけど、何も考えずに使うとメモリリークの原因になるんですね。 クリーンアップ関数の大切さがよく分かりました。」
先生
「そうですね。Reactでは処理を書くことよりも、 処理を終わらせることが大事な場面も多いんです。 特に非同期処理やイベントは注意が必要ですね。」
生徒
「これからはuseEffectを書くときに、 ちゃんとreturnでクリーンアップを書いているか確認するようにします。」
先生
「それができれば十分です。 クリーンアップを習慣にすれば、Reactのエラーや警告も減り、 安定したアプリが作れるようになりますよ。」
今回のまとめとして、ReactのuseEffectとクリーンアップ関数は、
メモリリークを防ぎ、安心してアプリを動かすための基本中の基本です。
小さなサンプルからでも意識して取り入れることで、
実務レベルでも通用するReactの書き方が自然と身についていきます。