Reactでライフサイクルを利用したイベントリスナー管理を初心者向けに解説!
生徒
「Reactで画面のスクロールやウィンドウのリサイズなどのイベントを監視したいときはどうすればいいですか?」
先生
「その場合は、ライフサイクルを意識してイベントリスナーを登録・解除すると効率的です。」
生徒
「ライフサイクルを意識するってどういう意味ですか?」
先生
「コンポーネントが画面に表示されるとき、更新されるとき、消えるときのタイミングに合わせてイベントの登録や解除を行うことを指します。」
1. クラスコンポーネントでのイベントリスナー管理
クラスコンポーネントでは、componentDidMountでイベントリスナーを登録し、componentWillUnmountで解除するのが基本です。これにより、不要なイベントが残らず、パフォーマンスやメモリの効率が向上します。
import React from "react";
class ScrollTracker extends React.Component {
state = { scrollY: 0 };
handleScroll = () => {
this.setState({ scrollY: window.scrollY });
};
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
render() {
return <p>スクロール位置: {this.state.scrollY}px</p>;
}
}
export default ScrollTracker;
2. 関数コンポーネントでのイベントリスナー管理
関数コンポーネントでは、useEffectを使ってライフサイクルを管理します。イベントリスナーの登録と解除は、useEffect内で行い、クリーンアップ関数で解除するのがポイントです。
import React, { useState, useEffect } from "react";
function ScrollTracker() {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll); // クリーンアップ
};
}, []);
return <p>スクロール位置: {scrollY}px</p>;
}
export default ScrollTracker;
3. ライフサイクルを意識したイベント管理のポイント
イベントリスナー管理で注意すべきポイントは以下です。
- コンポーネントが表示されたタイミングでイベントを登録する
- 不要になったときに必ず解除する
- 関数コンポーネントでは
useEffectのクリーンアップ関数を活用する - 複数のイベントを管理する場合も、登録と解除の対を揃える
これにより、メモリリークやパフォーマンスの低下を防ぎ、Reactアプリを安定して動作させることができます。
4. 実践的な応用例
例えば、ウィンドウのリサイズを監視して画面幅に応じたレイアウト変更を行う場合も同じです。
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <p>ウィンドウ幅: {width}px</p>;
}
5. メリットまとめ
- 不要なイベントが残らずメモリ消費を抑えられる
- ライフサイクルに沿った管理でパフォーマンス向上
- アプリが安定して動作する
- 副作用の管理が明確になる
Reactでイベントリスナーを管理するときは、ライフサイクルやuseEffectのクリーンアップ関数を意識することが重要です。
まとめ
Reactにおけるライフサイクルを利用したイベントリスナー管理は、モダンなWebフロントエンド開発において避けては通れない非常に重要なトピックです。特に、スクロール、リサイズ、キー入力、マウスの動きといったブラウザ固有のイベントを扱う際には、コンポーネントが生成される「マウント時」にリスナーを登録し、破棄される「アンマウント時」に正しく解除するという一連の流れを徹底する必要があります。
もし、この解除(クリーンアップ)を忘れてしまうと、コンポーネントが画面から消えた後もバックグラウンドで処理が走り続け、動作が重くなる「メモリリーク」の原因となります。特にSPA(シングルページアプリケーション)では、ページ遷移を繰り返すうちに不要なリスナーが溜まってしまい、最終的にブラウザがクラッシュすることもあるため注意が必要です。
React Hooksによる高度なイベント制御
現代のReact開発では、クラスコンポーネントよりも useEffect フックを用いた関数コンポーネントが主流です。useEffect の第2引数に空の依存配列 [] を渡すことで、コンポーネントの初回表示時のみ実行させることができます。そして、その中で return () => { ... } という形式で関数を返すことにより、安全にクリーンアップ処理を記述できるのが大きなメリットです。
実践的な実装例:複数イベントとカスタムフック化
より実践的な現場では、複数のイベントを同時に監視したり、共通のロジックを「カスタムフック」として切り出したりすることがよくあります。以下に、マウスの座標を取得する実践的なサンプルコードを示します。
import React, { useState, useEffect } from "react";
/**
* マウスの現在位置をリアルタイムで追跡するコンポーネント
*/
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
// マウス移動時のイベントハンドラ
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
console.log("マウスが動いています...");
};
// イベントリスナーの登録(マウント時)
window.addEventListener("mousemove", handleMouseMove);
// クリーンアップ関数(アンマウント時に実行)
return () => {
window.removeEventListener("mousemove", handleMouseMove);
console.log("リスナーを解除しました");
};
}, []); // 依存配列を空にすることで1回だけ実行
return (
<div className="p-4 border rounded bg-light">
<h4 className="text-primary">マウス位置の計測中</h4>
<p>現在のX座標: <strong>{position.x}px</strong></p>
<p>現在のY座標: <strong>{position.y}px</strong></p>
</div>
);
}
export default MouseTracker;
SEOとアクセシビリティへの配慮
フロントエンドエンジニアとして意識すべきは、コードの書き方だけではありません。イベントリスナーを多用する動的なUIは、検索エンジン(Googleなど)のクローラにとっても「正しく理解できるコンテンツ」である必要があります。Reactでリサイズイベントなどを利用してレイアウトを変更する場合、CSSのメディアクエリ(Media Queries)で解決できる部分はCSSに任せ、JavaScriptが必要なインタラクションのみを useEffect で実装するという使い分けが、表示速度の改善やSEO対策において非常に有効です。
また、イベントの頻度が高い(リサイズやスクロールなど)場合は、 throttle(間引き)や debounce(遅延実行)といった技術を組み合わせて、ブラウザの負荷をさらに軽減することも検討しましょう。
エンジニアとしてのステップアップ
この記事で紹介した基礎をマスターすれば、次はカスタムフックを作成してみましょう。例えば useWindowSize や useScrollPosition といった自作フックを作ることで、プロジェクト全体のコードの再利用性が劇的に向上します。Reactの公式ドキュメントでも推奨されている通り、ロジックと表示を切り離す設計思想を持つことが、プロ級のエンジニアへの近道となります。
生徒
「先生、ありがとうございました!イベントリスナーの登録だけじゃなくて、ちゃんと『後片付け』をするのがReactの鉄則なんですね。今までクリーンアップ関数を書き忘れていた気がします…。」
先生
「そうだよ。特に大規模なアプリケーションになると、その小さな『忘れ物』が積み重なって、動作がカクカクしたり、最悪の場合はブラウザが動かなくなったりするんだ。掃除をしない部屋にゴミが溜まっていくのと一緒だね。」
生徒
「なるほど、分かりやすい例えですね!useEffectの中で return を使って関数を返すだけで、勝手にコンポーネントが消える時に実行してくれるのは便利です。でも、もし依存配列(useEffectの第2引数)に変数を入れた場合はどうなるんですか?」
先生
「鋭いね!依存配列に変数を入れた場合は、その変数が更新されるたびに、まず『前の状態のクリーンアップ』が実行されてから、新しいリスナーが登録される仕組みになっているんだ。常に最新の状態を維持できるのがReactの凄いところだね。」
生徒
「つまり、変数が変わるたびに古いイベントは捨てて、新しいイベントを付け直すってことですね。これでスクロールイベントやリサイズイベントの実装にも自信がつきました!」
先生
「その意気だ。実際の開発では、Lodashのthrottleなどを使って、イベントの発火頻度を抑える工夫もあわせて学ぶと、さらにユーザーにとって使い心地の良いアプリが作れるようになるよ。次はカスタムフックの自作に挑戦してみようか!」
生徒
「はい!もっとReactを使いこなせるように頑張ります!」