Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Coding Challenge Practice - Question 20

2025年09月30日に「Dev.to」が公開したITニュース「Coding Challenge Practice - Question 20」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Reactで、要素外クリック時にドロップダウンなどを閉じる`useClickOutside`フックを実装する。`useRef`で要素を参照し、`useEffect`でマウスイベントリスナーを設定・解除。クリック位置を判定し、要素外であれば指定のコールバック関数を実行する。

出典: Coding Challenge Practice - Question 20 | Dev.to公開日:

ITニュース解説

このニュース記事は、ウェブアプリケーション開発でよく使われる「ドロップダウンメニュー」や「モーダルウィンドウ」といったUI要素を、その外側がクリックされたときに自動的に閉じるためのプログラミングテクニックについて解説している。具体的には、ReactというJavaScriptライブラリを使った開発において、「useClickOutside」という名前の「カスタムフック」をどのように実装するかを示している。このカスタムフックは、特定のDOM要素の外側をクリックしたことを検知し、あらかじめ指定された処理(例えばドロップダウンを閉じる処理)を実行する機能を提供するものだ。

まず、Reactにおける「フック」とは、関数コンポーネントの中でステート(状態)やライフサイクル(コンポーネントの生成から消滅までの流れ)といったReactの機能を使えるようにするための特別な関数である。そして、「カスタムフック」とは、複数のコンポーネント間で共通のロジックを再利用できるように、開発者が独自に作成するフックのことを指す。useClickOutsideはまさにこのようなカスタムフックの典型例と言える。

このカスタムフックを実装する上で、主に二つのReactフックが使われている。一つは「useRef」で、もう一つは「useEffect」だ。

useRefは、DOM要素への参照、つまりHTML要素そのものへのポインターを保持するために使われるフックである。Reactでは通常、直接DOMを操作することは推奨されないが、特定のケース、例えば外部のライブラリと連携する場合や、今回のように特定の要素のサイズや位置を取得したり、イベントリスナーを直接アタッチしたりする場合には、useRefが非常に有用となる。useClickOutsideでは、外側クリックを検知したい対象の要素(ドロップダウンメニューなど)への参照を保持するためにconst ref = useRef<T>(null)という形で利用されている。ここで<T extends HTMLElement>とあるのは、このrefがHTML要素を参照することを型レベルで示している。初期値はnullだが、実際にDOM要素に割り当てられると、ref.currentを通じてその要素にアクセスできるようになる。

次に、useEffectは、コンポーネントのレンダリング(描画)とは別に実行される「副作用」を扱うためのフックである。例えば、データ取得、DOMの直接操作、そして今回のようにイベントリスナーの設定と解除などがこれにあたる。useEffectは、コンポーネントがマウントされたとき(初めて画面に表示されたとき)、更新されたとき、そしてアンマウントされたとき(画面から削除されたとき)に特定の処理を実行できるようにする。

useClickOutsideカスタムフックの具体的な実装を見ていこう。まず、useRefで作成したrefが、監視対象のDOM要素への参照を保持する。そして、useEffectの中にイベントリスナーの登録と解除のロジックが書かれている。これは、コンポーネントが画面に表示されたときにイベントリスナーを登録し、画面から消えたときにイベントリスナーを解除することで、不要なメモリ使用や予期せぬ動作を防ぐためのベストプラクティスである。

イベントリスナーが検知するクリックイベントは、handleClickという関数によって処理される。このhandleClick関数は、クリックイベントオブジェクトeを受け取り、そのe.target(クリックされた具体的なDOM要素)が、ref.current(監視対象の要素)の「内側」にあるかどうかを判定する。ref.current && !ref.current.contains(e.target as Node)という条件式がその判定を行っている。 ここでref.currentは、監視対象のDOM要素が存在することを確認している。もし要素がまだDOMにマウントされていない場合(ref.currentnullの場合)は、後続の処理を行わないことでエラーを防ぐ。 ref.current.contains(e.target as Node)は、監視対象の要素がクリックされた要素を含んでいるかどうか、つまりクリックが監視対象の要素の「内側」で行われたかどうかをブール値(真偽値)で返すメソッドである。 そして、この結果に!(否定演算子)が付いているため、この条件式全体は「監視対象の要素が存在し、かつ、クリックが監視対象の要素の『内側ではない』場所で行われた場合」に真となる。つまり、「外側がクリックされた場合」を正確に判定しているのだ。 この条件が真となった場合、引数として渡されたcallback()関数が実行される。このcallback関数こそが、ドロップダウンメニューを閉じるなどの実際の処理を行う部分である。

useEffectの中では、document.addEventListener('mousedown', handleClick)を使って、ウェブページ全体でマウスの押し下げイベントを監視するように設定している。mousedownイベントはclickイベントよりも早く発生するため、UIのレスポンスを向上させるのに適している場合がある。 そして、useEffectが返す関数はクリーンアップ関数と呼ばれ、コンポーネントがアンマウントされるときや、依存配列(この場合は[callback])が変更されてuseEffectが再実行される前に呼び出される。ここでdocument.removeEventListener('mousedown', handleClick)を実行することで、不要になったイベントリスナーを適切に削除し、メモリリークを防いでいる。依存配列に[callback]を含めるのは、callback関数が変更された場合に、最新のcallback関数を持つhandleClickがイベントリスナーに再登録されるようにするためである。これにより、常に正しい閉じる処理が実行されることが保証される。

最後に、このuseClickOutsideカスタムフックはrefを返している。これは、このフックを使う側が、どのDOM要素に対して外側クリックを検知したいのかを、返されたrefをその要素のrefプロパティに渡すことで指定できるようにするためである。例えば、<div ref={myClickOutsideRef}>のように使うことで、このdiv要素がuseClickOutsideの監視対象となる。

このように、useClickOutsideカスタムフックは、ReactのuseRefuseEffectを組み合わせて、特定の要素の外側クリックを効率的かつ安全に検知する汎用的な仕組みを提供している。これは、ユーザーインターフェースのインタラクティブ性を高め、より良いユーザー体験を実現するために、システムエンジニアが身につけておくべき重要なテクニックの一つと言えるだろう。

関連コンテンツ

関連IT用語