ReactのcomponentWillUnmountとクリーンアップ関数を徹底解説!初心者でもわかるライフサイクルの基本
生徒
「先生、ReactのcomponentWillUnmountって何をするものなんですか?」
先生
「良い質問ですね。componentWillUnmountは、コンポーネントが画面から消えるときに実行される関数なんです。」
生徒
「画面から消えるときって、どういうときですか?」
先生
「例えば、別のページに移動したり、条件分岐でコンポーネントを非表示にしたときなどですね。そのときに後片付けをするのがcomponentWillUnmountの役目です。」
生徒
「なるほど。じゃあ、関数コンポーネントではどうやって後片付けをするんですか?」
先生
「関数コンポーネントでは、useEffectというフックの中で“クリーンアップ関数”を使います。これがcomponentWillUnmountと同じ役割を果たしますよ。」
1. componentWillUnmountとは?
Reactのクラスコンポーネントでは、ライフサイクルメソッドと呼ばれる関数を使って、コンポーネントの動作タイミングを制御できます。その中のひとつがcomponentWillUnmountです。
componentWillUnmountは、コンポーネントが画面から削除される直前に実行されるメソッドで、主に「後片付け」を行うために使われます。
たとえば、以下のような場合に利用します:
- タイマー(setIntervalやsetTimeout)の停止
- イベントリスナー(addEventListener)の削除
- API通信のキャンセル
- WebSocketや外部リソースのクローズ処理
import React from "react";
class Timer extends React.Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log("タイマーが動作中");
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
console.log("タイマーを停止しました");
}
render() {
return <h1>タイマー動作中...</h1>;
}
}
export default Timer;
2. 関数コンポーネントでのクリーンアップ関数
React Hooks(フック)を使った関数コンポーネントでは、クラスのようにcomponentWillUnmountを使うことはできません。その代わりに、useEffectフックの中で「クリーンアップ関数」を定義します。
クリーンアップ関数とは、useEffectの中でreturn文を使って定義する関数のことです。この関数は、コンポーネントが削除されるとき、または依存する値が変わる前に自動的に呼び出されます。
import React, { useEffect } from "react";
function Timer() {
useEffect(() => {
const timer = setInterval(() => {
console.log("タイマーが動作中");
}, 1000);
// クリーンアップ関数
return () => {
clearInterval(timer);
console.log("タイマーを停止しました");
};
}, []);
return <h1>タイマー動作中...</h1>;
}
export default Timer;
つまり、クリーンアップ関数は「後片付け専門の関数」であり、componentWillUnmountと同じ役割を果たします。関数コンポーネントではこの方法でリソースをきれいに解放します。
3. クリーンアップ関数が呼ばれるタイミング
クリーンアップ関数は、次の2つのタイミングで呼び出されます。
- コンポーネントがアンマウント(削除)されるとき
useEffectの依存配列(第2引数)の値が変化する直前
依存配列とは、useEffect(() => {}, [値])の中の[値]部分です。この配列に指定された変数が変わると、古いエフェクトがクリーンアップされ、新しいエフェクトが実行されます。
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("カウントが変更されました:", count);
return () => {
console.log("前のカウントをクリーンアップ:", count);
};
}, [count]);
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>カウントを増やす</button>
</div>
);
}
export default Example;
このようにクリーンアップ関数を使うことで、Reactは効率的にメモリを管理し、不要な動作を防ぐことができます。
4. componentWillUnmountとクリーンアップ関数の違い
| 項目 | componentWillUnmount(クラス) | クリーンアップ関数(関数コンポーネント) |
|---|---|---|
| 使う場所 | クラスコンポーネント内 | useEffectの中 |
| 呼ばれるタイミング | コンポーネントが削除される直前 | 削除時・依存値が更新される直前 |
| 記述方法 | メソッドとして定義 | return文で定義 |
どちらも「後片付け」をするという点では同じですが、記述の仕方と使う場所が異なります。React Hooksを使う現在では、クリーンアップ関数を使う書き方が主流です。
5. クリーンアップ関数の実践的な使い方
最後に、実際の開発でよくある使い方を紹介します。たとえば、ブラウザのイベントを監視している場合、コンポーネントが削除されるときにリスナーを解除しないと、不要な処理が残ってしまいます。
import React, { useEffect } from "react";
function WindowResizeLogger() {
useEffect(() => {
const handleResize = () => {
console.log("ウィンドウサイズ:", window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
console.log("リスナーを解除しました");
};
}, []);
return <h1>ウィンドウサイズを監視中...</h1>;
}
export default WindowResizeLogger;
このようにクリーンアップ関数は、アプリの安定性やパフォーマンスを保つうえで非常に重要な役割を果たします。
まとめ
Reactの開発において、コンポーネントの「後片付け」を正しく理解することは、アプリケーションの品質を左右する非常に重要なステップです。今回は、クラスコンポーネント時代のcomponentWillUnmountから、現代の主流である関数コンポーネントでのuseEffectを用いたクリーンアップ関数まで、その役割と使い方を詳しく解説してきました。
特に初心者の方が躓きやすいポイントは、「なぜ後片付けが必要なのか」という点です。JavaScriptのタイマー処理やイベントリスナー、外部APIとの通信などは、コンポーネントが画面から消えた後もブラウザのメモリ上に残り続けてしまうことがあります。これを放置すると、メモリリークの原因となり、アプリが次第に重くなったり、予期せぬエラーを引き起こしたりします。プログラミングにおいて、開いたドアを閉める、使った道具を片付けるといった「終わりの儀式」をコードで表現するのが、クリーンアップの役割なのです。
学んだポイントの振り返り
- componentWillUnmountの役割:コンポーネントが破棄される直前に一度だけ実行される「お別れの挨拶」のようなメソッド。
- useEffectでの代替:関数コンポーネントでは、
useEffectの戻り値(return)に「クリーンアップ関数」を指定することで、同様の処理を実現。 - 実行のタイミング:アンマウント時だけでなく、依存配列(Dependency Array)の値が更新される直前にも、古い状態をリセットするために実行される。
- 実践的な活用例:
setIntervalの解除、addEventListenerの削除、そして不要になったAPIリクエストの中断などが代表的。
具体的な実装例:複数条件でのクリーンアップ
より実践的なコードを見てみましょう。例えば、ユーザーIDが変更されるたびにデータを取得し、その間にコンポーネントが切り替わったりIDが変わったりした場合のクリーンアップは以下のように記述します。
import React, { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
let isMounted = true; // コンポーネントがマウントされているかどうかのフラグ
console.log(`ユーザー ${userId} のデータを取得開始...`);
// 擬似的なAPIコール
fetch(`https://api.example.com/user/${userId}`)
.then(response => response.json())
.then(data => {
if (isMounted) {
setUserData(data);
console.log("データをセットしました");
}
});
// クリーンアップ関数
return () => {
isMounted = false; // アンマウント時にフラグを倒す
console.log(`ユーザー ${userId} の処理を中断または破棄しました`);
};
}, [userId]); // userIdが変わるたびに実行
return (
<div>
{userData ? (
<h1>ユーザー名: {userData.name}</h1>
) : (
<p>読み込み中...</p>
)}
</div>
);
}
export default UserProfile;
このように、useEffectを活用することで、クラスコンポーネントよりも柔軟かつ簡潔に「副作用(Side Effects)」を管理できるようになります。これからのモダンなReact開発では、フック(Hooks)を使いこなし、リソースを適切に解放する癖をつけておきましょう。
生徒
「先生、まとめを読んでクリーンアップ関数の重要性がさらに深く理解できました!要するに、使いっぱなしにしないで『お片付け』をするのがマナーだってことですね。」
先生
「その通りです!リアルの世界と同じで、出しっぱなしにすると部屋(メモリ)が散らかってしまいますからね。プログラミングにおけるマナーは、アプリのパフォーマンスに直結するんですよ。」
生徒
「コードの中でreturn () => { ... }と書くだけで、古い処理をリセットできるのは便利ですね。でも、依存配列の[userId]がある場合とない場合では、動きが全然違うんですよね?」
先生
「よく気づきましたね。空の配列[]ならアンマウント時だけですが、値が入っていると、その値が変わるたびに『古い方のクリーンアップ』と『新しい方のエフェクト』が交互に走ります。これがReactの賢いところです。」
生徒
「なるほど。だから前のカウントや前の通信をキャンセルできるんですね。最初は少し複雑に感じましたが、実際に動かしてみると納得がいきました。これからはaddEventListenerを使ったら必ずセットでremoveEventListenerを書くようにします!」
先生
「素晴らしい意気込みですね。その積み重ねが、バグの少ない、ユーザーに優しいアプリケーション作りへと繋がります。ライフサイクルをマスターすれば、中級者への道もすぐそこですよ!」