こんにちは!Reactの世界へようこそ。チーフアーキテクトの私です。
Reactを触り始めて少し経つと、必ずと言っていいほど出会うのが、この`useEffect`というフック、そしてその中に潜む「クリーンアップ関数」という少しお堅い名前の仕組みです。
公式ドキュメントを読んでも「アンマウント時」「依存値の変更直前」なんて、エンジニア特有の冷たい言葉が並んでいて、初めて見たときは「うっ……難しそう……」とそっとブラウザを閉じちゃった方も多いのではないでしょうか。大丈夫、安心してください。今日は難しい専門用語はなるべく置いておいて、身近な例えと一緒に、このクリーンアップ関数の本質を優しく紐解いていきましょう。
—
クリーンアップ関数って、なに?(身近な例えでお話しします)
まずは、イメージを膨らませるために、こんな日常のワンシーンを想像してみてください。
あなたは休日、お部屋でDIYをしようと道具箱から色々な工具を取り出しました。
- ペンキを塗るために、フタを開けました。
- 音楽をかけるために、ポータブルスピーカーの電源を入れました。
- 手元を明るくするために、デスクライトをつけました。
さて、夕方になって作業が終わり、リビングへ移動する時間になりました。このとき、あなたはどうしますか?
「やったー、終わり!」と、ペンキのフタを開けっぱなし、音楽を大音量で鳴らしっぱなし、ライトもつけっぱなしのまま、部屋を飛び出したりはしませんよね。
ちゃんと後片付けをしますよね。
- ペンキのフタをカチッと閉める。
- スピーカーの電源をオフにする。
- デスクライトのスイッチを切る。
この「作業が終わったあとの後片付け」こそが、Reactにおけるクリーンアップ関数そのものなんです。
—
なぜ「お片付け」が必要なの?(メモリリークという名のホラー)
Reactのコンポーネント(画面のパーツ)は、画面に現れたり(マウント)、消えたり(アンマウント)を結構ひんぱんに繰り返しています。
もし、画面から消えたあとのコンポーネントが、裏側でこっそり動き続けていたらどうなるでしょう?
例えば……
- 「もう消えたはずの画面なのに、サーバーへの通信を何秒おきにか送り続けている」
- 「イベントリスナー(キーボードの入力監視など)が登録されたままで、ブラウザのメモリをどんどん食いつぶしていく」
こうした「見えないところでリソースがムダに消費され続ける現象」を、業界ではメモリリーク(メモリのしずく漏れ)と呼びます。最悪の場合、ブラウザがフリーズしたり、動作がカクカクになったりする原因になります。
だからこそ、「この画面、もう用なしだから消すね!」というタイミングで、裏で動いていたタイマーや通信、イベントをきれいに掃除(クリーンアップ)してあげる必要があるのです。
—
コードで見てみよう!タイマーの例
百聞は一見にしかず。実際のコードを見てみましょう。
ここでは、「画面を開いてからの経過時間を1秒ごとに数えてくれるタイマー」を作ってみます。お片付け(クリーンアップ)を忘れるとどうなるかも含めて見てみましょう。
import React, { useState, useEffect } from ‘react’;
function TimerComponent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 【お仕事の開始】1秒ごとに秒数を増やすタイマーをセットするよ
const timerId = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
console.log(‘タイマーを起動しました!’);
// ==========================================
// 【ここがクリーンアップ関数!】
// このコンポーネントが画面から消えるとき、
// または次にuseEffectが走る直前に、自動的に実行されます。
// ==========================================
return () => {
clearInterval(timerId); // 動かしていたタイマーをしっかり止める(お片付け!)
console.log(‘タイマーをきれいに片付けました!’);
};
}, []); // 依存配列が空なので、画面に出たときの1回だけ動きます
return (
タイマーのテスト画面
経過時間: {seconds} 秒
);
}
export default TimerComponent;
このコードのミソは、`useEffect` の中で「関数をreturn(返却)している」という点です。
「えっ、関数の中で関数を返すの?」と最初はギョッとするかもしれませんが、Reactはこの「返された関数」の存在を見逃しません。コンポーネントが役目を終えて画面から消えるとき、そっとこのお片付け関数を実行してくれます。
—
いつ、どんなタイミングで実行されるの?
クリーンアップ関数が呼ばれるタイミングは、大きく分けて次の2つです。
1. コンポーネントが画面から消えるとき(アンマウント時)
- さきほどの例のように、別のページに移動したり、条件分岐でそのコンポーネント自体が表示されなくなったとき。
2. 依存する値が変わる直前(再実行の直前)
- 例えば `useEffect` の依存配列に `[userId]` を指定していて、ユーザーのIDが「Aさん」から「Bさん」に切り替わったとします。
- このとき、Reactは「あ、Aさん用の古い通信や監視はもういらないから、まず片付けてから、新しいBさん用の処理を始めよう」と判断します。新しい処理を始める直前に、古いクリーンアップ関数が走るのです。
—
実務でよくあるつまずきポイントと、温かいアドバイス
初心者の頃、私もよくやってしまった失敗があります。それは「クリーンアップを書き忘れて、コンソールがエラーの嵐になる」という現象です。
特に、非同期通信(`fetch` や `axios` など)を行っている最中にユーザーがページをパッと切り替えてしまうと、Reactは怒ります:
> “Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak.”
> (おいおい、もう画面から消えたコンポーネントのデータ状態を書き換えようとしてるよ! メモリリークの兆候だから気をつけてね!)
この赤字のエラーを見たとき、「うわ、なんか壊しちゃった!」と焦る必要はありません。Reactからの「ねえ、ちゃんと最後のお片付けをしてってよ!」という優しいアラートだと思って受け取ってください。
もし通信の途中で画面が消えてしまったら、以下のように「今、画面にいるかどうか(フラグ)」を管理してあげるのも、実務でよく使われるテクニックです。
useEffect(() => {
let isMounted = true; // 画面にいるよフラグ
fetchUserData().then((data) => {
if (isMounted) {
// まだ画面にいるときだけデータをセットする
setUserData(data);
}
});
return () => {
isMounted = false; // 画面から消えたらフラグを折る!
};
}, [userId]);
—
最後に。
最初は「副作用」「クリーンアップ」「依存配列」と、呪文のような言葉に圧倒されるかもしれませんが、本質はとってもシンプルです。
「何かを始めたら、終わるときにちゃんと片付ける」。
私たちが日常生活で当たり前にやっていることを、Reactのコードの上でも優しく再現してあげているだけなんですよね。
もしコードを書いていて「あれ、なんか動きがおかしいな」と思ったら、まずはこのお片付け(クリーンアップ関数)がちゃんと機能しているか、`console.log` を仕込んで覗いてみてください。
きっと、「あ、ここで片付けられてなかったんだな」と、原因がスッと見えてくるはずです。
あなたのReact開発が、少しでも楽しく、ワクワクするものになりますように。それではまた次の現場でお会いしましょう!

コメント