Reactのカスタムフックでイベントリスナーを共通化する方法!初心者でも理解できるReact Hooks入門
生徒
「Reactでスクロールやクリックなどのイベントを複数のコンポーネントで使いたいとき、毎回書くのが面倒なんですが…」
先生
「それは良いところに気づきました。そんなときに使えるのが『カスタムフック』です。」
生徒
「カスタムフックって、自分で作るReactのフックのことですか?」
先生
「その通りです。今回は、イベントリスナー(イベントを検知して動作する仕組み)をカスタムフックで共通化する方法を学びましょう。」
1. カスタムフックとは?
まず「カスタムフック(Custom Hook)」とは、Reactで用意されているuseStateやuseEffectなどのフックを組み合わせて、
自分専用の「use〇〇」という関数を作る仕組みのことです。Reactでは、同じ処理を何度も書くよりも、共通化して再利用する考え方がとても重要になります。
たとえば「画面の操作を監視する」「状態を管理する」といった処理を、コンポーネントごとに書いていると、コードが長くなり理解しづらくなります。 カスタムフックを使えば、そのような処理をひとまとめにでき、初心者でも読みやすく整理されたReactコードを書くことができます。
イベントリスナーのように「何かの動きを検知して処理を行う」仕組みは、カスタムフックと非常に相性が良く、 一度作っておけば複数のコンポーネントで安全に使い回せるようになります。
import { useState } from "react";
function useMessage() {
const [message, setMessage] = useState("こんにちは!");
return { message, setMessage };
}
export default useMessage;
このように、カスタムフックは「処理のまとまり」を作るための仕組みです。 React初心者の方は、まずは「同じ処理を何度も書いていないか」を意識しながら、 少しずつカスタムフックに分ける練習をすると理解が深まります。
2. イベントリスナーとは?
「イベントリスナー」とは、ユーザーが画面上で行った操作をきっかけに、あらかじめ決めておいた処理を実行する仕組みのことです。 たとえば、ボタンをクリックしたとき、画面をスクロールしたとき、キーボードを押したときなど、 Webページ上で起こるさまざまな動きを検知して反応する役割を持っています。
プログラミング未経験の方でも、「ボタンを押したら文字が変わる」「マウスを動かしたら表示が変わる」といった動きは、 日常的にWebサイトで見かけているはずです。これらの裏側では、イベントリスナーがユーザーの操作を常に監視し、 決められたタイミングで処理を呼び出しています。
import React, { useState } from "react";
function ClickMessage() {
const [text, setText] = useState("こんにちは!");
function handleClick() {
setText("ボタンがクリックされました!");
}
return (
<div style={{ textAlign: "center" }}>
<p>{text}</p>
<button onClick={handleClick}>クリック</button>
</div>
);
}
export default ClickMessage;
この例では、「クリックする」というイベントに反応して文字を変更しています。 Reactでは、このようなイベント処理をコンポーネントの中に書くことができますが、 同じようなイベントリスナーを複数のコンポーネントで使い始めると、コードが増えて管理が難しくなります。
そのため、イベントリスナーをまとめて扱える仕組みが重要になります。 後ほど紹介するカスタムフックを使えば、イベント処理を共通化し、 Reactアプリ全体をシンプルで分かりやすい構造に整理できるようになります。
3. useEventListenerフックを作ってみよう
まずは、どんなイベントでも簡単に登録できるような汎用的なカスタムフックを作ります。
import { useEffect } from "react";
function useEventListener(eventType, handler, element = window) {
useEffect(() => {
if (!element) return;
// イベントを登録
element.addEventListener(eventType, handler);
// クリーンアップ(コンポーネントが消えるときに解除)
return () => {
element.removeEventListener(eventType, handler);
};
}, [eventType, handler, element]);
}
export default useEventListener;
このカスタムフックでは、useEffectを使って、イベントの登録と解除を自動で行っています。
つまり、このフックを使うだけで、コンポーネントごとに同じイベントリスナーを書く必要がなくなります。
4. カスタムフックを使ってスクロールイベントを共通化する
次に、このuseEventListenerを使って、スクロール量を監視する例を見てみましょう。
import React, { useState } from "react";
import useEventListener from "./useEventListener";
function ScrollTracker() {
const [scrollY, setScrollY] = useState(0);
// スクロールイベントを監視して状態を更新
useEventListener("scroll", () => {
setScrollY(window.scrollY);
});
return (
<div style={{ height: "200vh", padding: "20px" }}>
<h2>現在のスクロール位置: {scrollY}px</h2>
</div>
);
}
export default ScrollTracker;
このように、useEventListenerを使えば、イベント処理をシンプルに書けるようになります。
5. 複数のイベントでも使える便利な共通化
このuseEventListenerフックは、クリックやキー入力などにも使えます。
import React, { useState } from "react";
import useEventListener from "./useEventListener";
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
// マウスの動きを監視
useEventListener("mousemove", (e) => {
setPosition({ x: e.clientX, y: e.clientY });
});
return (
<div style={{ height: "100vh", textAlign: "center" }}>
<h2>マウス位置: X={position.x}, Y={position.y}</h2>
</div>
);
}
export default MouseTracker;
このように、同じuseEventListenerを使い回せるので、コードの重複が減り、保守性(あとで修正しやすくすること)も向上します。
6. カスタムフックでコードを整理するメリット
カスタムフックでイベントリスナーを共通化するメリットは次のとおりです。
- ① コードの重複を減らせる
- ② イベントの登録・解除を自動で行える
- ③ コンポーネントのロジックをスッキリ整理できる
たとえば、通常ならuseEffectでイベントを登録し、クリーンアップするコードを毎回書く必要がありますが、useEventListenerなら1行で済みます。
プログラムの保守やバグ修正が簡単になるため、実際の現場でもよく使われる設計パターンです。
7. イベント対象を自由に変える応用例
さらに、第三引数に特定の要素(例えばdocumentや特定のDOM要素)を渡すことで、より柔軟な制御が可能になります。
import React, { useRef, useState } from "react";
import useEventListener from "./useEventListener";
function BoxClickTracker() {
const boxRef = useRef(null);
const [count, setCount] = useState(0);
// 特定の要素(boxRef)にクリックイベントを追加
useEventListener("click", () => setCount(count + 1), boxRef.current);
return (
<div>
<div
ref={boxRef}
style={{
width: "200px",
height: "200px",
backgroundColor: "lightblue",
margin: "20px auto",
}}
></div>
<h2>クリック回数: {count}</h2>
</div>
);
}
export default BoxClickTracker;
このように、ウィンドウ全体だけでなく特定の要素にもイベントを付与できるのが大きな特徴です。
8. ポイント整理
ここまで見てきたように、イベントリスナーをカスタムフックとして共通化すると、Reactアプリ全体の構造がとても分かりやすくなります。 クリックやスクロール、マウス操作といったイベント処理は、初心者のうちはコンポーネントごとに直接書きがちですが、 数が増えるにつれて「どこで何をしているのか」が把握しづらくなります。
useEventListenerのようなカスタムフックを一つ用意しておけば、
イベントの登録や解除といった細かい処理を意識せずに使えるため、
コンポーネント側は「何をしたいか」だけを書く形になります。
これは、プログラミング未経験者がReactの考え方に慣れるうえでも大きなメリットです。
import React, { useState } from "react";
import useEventListener from "./useEventListener";
function SimpleButton() {
const [message, setMessage] = useState("こんにちは!");
useEventListener("click", () => {
setMessage("ボタンがクリックされました!");
});
return (
<div style={{ textAlign: "center" }}>
<p>{message}</p>
<button>クリック</button>
</div>
);
}
export default SimpleButton;
このように、イベント処理をカスタムフックに任せることで、 Reactのコードは短く、読みやすく、修正しやすい形になります。 最初は難しく感じるかもしれませんが、「同じ処理が何度も出てきたらまとめられないか」 という視点を持つことが、React上達への大切な一歩です。
まとめ
Reactのカスタムフックとイベントリスナーを振り返る
ここまでの記事では、Reactにおけるカスタムフックの基本から始まり、イベントリスナーを共通化する考え方や実装方法について段階的に学んできました。 React開発では、クリックやスクロール、マウス操作、キー入力といったイベント処理が頻繁に登場します。これらを各コンポーネントごとに個別に実装してしまうと、 同じようなコードが増え、可読性が下がるだけでなく、修正や保守の際にミスが発生しやすくなります。
そこで重要になるのが「カスタムフック」という考え方です。React Hooksの仕組みを活用し、共通処理をひとつの関数にまとめることで、 コンポーネントは表示や状態管理といった本来の役割に集中できるようになります。 特にイベントリスナーは、登録と解除を正しく行わないとメモリリークや意図しない動作につながるため、 カスタムフックで安全に管理する価値が非常に高い処理です。
useEventListenerフックで得られる実践的なメリット
記事内で作成したuseEventListenerフックは、イベントの種類、実行する処理、対象となる要素を引数として受け取るシンプルな構造でした。 しかし、このシンプルさこそが実務でも役立つポイントです。 スクロールイベント、クリックイベント、マウスムーブイベントなど、用途が異なっても同じフックを再利用できるため、 Reactアプリ全体の設計が統一され、コードの見通しが良くなります。
また、useEffectのクリーンアップ処理をフック内部に閉じ込めることで、 イベントリスナーの解除忘れといった初心者がつまずきやすいポイントも自然に回避できます。 このような設計は、React初心者だけでなく、中級者やチーム開発の現場でも非常に重要な考え方です。
まとめとしてのサンプル構成イメージ
function SampleComponent() {
useEventListener("scroll", () => {
console.log("スクロールされました");
});
return (
<div className="container">
<h2>イベントリスナー共通化のサンプル</h2>
</div>
);
}
このように、コンポーネント側では難しい処理を意識せず、必要なイベントを指定するだけで済む点が、 カスタムフックを使ったイベントリスナー共通化の大きな魅力です。
生徒:「最初はイベントリスナーって難しそうだと思っていましたが、カスタムフックにするとすごく分かりやすくなりました。」
先生:「それが狙いですね。Reactでは処理を部品として分けて考えることがとても大切です。」
生徒:「毎回useEffectを書くよりも、useEventListenerを使ったほうがミスも減りそうです。」
先生:「その通りです。イベントの登録と解除を共通化できるので、保守性も高くなります。」
生徒:「スクロールやクリック以外のイベントにも使えるのが便利ですね。」
先生:「はい。Reactのカスタムフックを理解できれば、今回のような応用はどんどん広がっていきます。」
生徒:「これからは、同じ処理を見つけたらカスタムフックにできないか考えてみます。」
先生:「それができるようになれば、React開発が一段と楽しくなりますよ。」