こんにちは!Reactの学習、毎日お疲れ様です。
画面を作っていて、「ウィンドウのサイズが変わったら、それに合わせてデザインを調整したいな」「画面のスクロール位置を検知したいな」と思ったことはありませんか?
そんなときによく使うのが、`window`や`document`に対する「イベントリスナー」です。
でも、Reactを触り始めたばかりの頃って、このイベントリスナーの登録と片付け(クリーンアップ)の作法で、なんだかモヤモヤしがちなんですよね。「あれ、動くには動くけど、なんかメモリを食いつぶしてる気がする…?」なんて不安になったり。
大丈夫ですよ!今日は、身近な「ある場所でのマナー」に例えながら、Reactでのイベントリスナーの安全な管理方法を一緒に紐解いていきましょう。
—
イベントリスナーは「お部屋のポスター」のようなもの
まず、イメージしやすいように例え話からさせてくださいね。
あなたの部屋(コンポーネント)に、お気に入りのポスター(イベントリスナー)を貼るとします。
ポスターを貼るということは、「誰かが『見て!』と言ったとき(イベント発生時)に、そのお知らせを聞き逃さないように耳を澄ませる状態にする」ということです。
ここで大事なマナーが2つあります。
1. 部屋に入ったら(画面が表示されたら)、ちゃんとポスターを貼る。
2. 部屋から出ていくとき(画面が消えるとき)は、ポスターをキレイに剥がして、元通り片付ける。
もし、2番目の「片付け」を忘れて部屋を退去してしまったらどうなるでしょう? 次の住人が困るし、あなたの部屋の壁にはポスターが貼りっぱなしになってしまいますよね。
Reactの世界でもこれと全く同じことが起きます。画面が消えているのに、見えないところでイベントリスナーが働き続けてしまうと、「メモリリーク(メモリの無駄遣い)」という、ちょっと厄介な不具合の原因になってしまうんです。
—
useEffectの力を借りてみよう
Reactでこの「貼る・剥がす」を安全に行うために用意されているのが、`useEffect`というフックです。
百聞は一見に如かず、まずは実際のコードを見てみましょう。今回は、「画面の幅(ウィンドウの横幅)が今いくつなのかをリアルタイムで表示する」というコンポーネントを作ってみます。
import { useState, useEffect } from ‘react’;
export default function WindowSizeChecker() {
// 1. ウィンドウの幅を保存しておく状態(state)を作ります
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
// 2. ウィンドウサイズが変わったときに実行したい処理を関数として書きます
const handleResize = () => {
console.log(‘画面サイズが変わったよ!’);
setWindowWidth(window.innerWidth);
};
// 3. 【お部屋に入った時】windowにイベントリスナーを登録します
window.addEventListener(‘resize’, handleResize);
// 4. 【お部屋から出る時】クリーンアップ関数でイベントリスナーをキレイに剥がします!
return () => {
window.removeEventListener(‘resize’, handleResize);
};
}, []); // 空の依存配列を渡すことで、「最初の一回だけ登録してね」と伝えています
return (
今のウィンドウの幅
{windowWidth} px
);
}
—
コードのポイントを優しく分解!
先ほどのコード、どこがどう動いているのか、大切なポイントを3つに分けてお話ししますね。
① `useEffect` の中の処理は「画面が表示された後」に動く
Reactのコンポーネントが画面に描画(レンダリング)された直後に、`useEffect`の中身が実行されます。ここで `window.addEventListener` を使って、「ねえブラウザさん、ウィンドウのサイズが変わったら教えてね」とお願い(登録)しているわけです。
② クリーンアップ関数(`return () => { … }`)が魔法のほうき
ここが今日一番お伝えしたい主役です!
`useEffect`の中で `return` を使って関数を返してあげると、「このコンポーネントが画面から消えるとき(アンマウントされるとき)」や「次にエフェクトが実行される直前」に、その関数が自動的に呼び出される仕組みになっています。
今回のコードでは、この `return` の中で `window.removeEventListener` を実行しています。これが、先ほどお話しした「部屋を出るときにポスターを剥がす」という作業です。これで安心ですね!
③ 依存配列 `[]` の意味
最後の `[]`(空の配列)は、「このコンポーネントが最初に画面にポコンと現れたとき(マウント時)に、1回だけイベントリスナーを登録してね。あとは画面が消えるまでそのままよろしく!」というおまじないです。ここを忘れると、毎秒リスナーが登録されて大変なことになってしまうので注意してくださいね。
—
つまずきやすいポイントと「大丈夫だよ」のフォロー
初心者の頃、私もここでめちゃくちゃ悩んだのですが、皆さんがこれから書くときに「あれ?」と思いやすいポイントを先回りしてシェアしておきますね。
- 「`removeEventListener` の中身、なんか難しそう…」
そうなんです。実は `removeEventListener` を使うときは、`addEventListener`のときと全く同じ関数(今回の場合は `handleResize`)を指定してあげないと、ブラウザが「どれを剥がせばいいの?」と迷って剥がしてくれません。 だからこそ、`useEffect`の中で関数を定義して、登録と解除で同じ関数を使い回すのが一番安全なんです。
- 「クリーンアップを書き忘れたらどうなるの?」
小さなアプリや、一瞬試すだけなら正直すぐに目に見えるバグは起きません。「あれ、書かなくても動くじゃん!」って思いがちです。
でも、ページを何度も行き来するような大きなアプリを作ったときに、これが積もり積もって動作が重くなったり、予期せぬエラー(「もう消えたはずの画面のデータを書き換えようとしていますよ」という警告など)の原因になります。だからこそ、「イベントリスナーを登録したら、セットで必ず剥がす癖」を今のうちにマインドとして持っておくと、将来の自分が泣いて喜びますよ!
—
まとめ
いかがでしたでしょうか?
- イベントリスナーを登録したら、必ずクリーンアップ(`removeEventListener`)で後片付けをする。
- その片付け場所として、`useEffect` の `return` が大活躍する。
最初は「覚えることが多くて大変だな…」と感じるかもしれませんが、何度か書いていくうちに、「あ、ここでポスターを貼って、ここで剥がすんだな」と手が自然に覚えていきます。
焦らず、一つひとつ自分のペースで進めていきましょうね。あなたのReactライフを、心から応援しています!

コメント