子要素クリック時の親要素のクリックイベントの挙動

業務でタイトルのことでちょっと詰まってた。
Reactの記述はだいぶできるようになったけど、JS使ったDOM操作は普段やらないからまだなかなか慣れない。

分からなくて隣のインド人に聞いたら悩んだ挙句、ちょっと複雑めな解決方法教えてくれた。
なるほど!と思ってググりながら実装してる途中でめちゃ簡単な方法を見つけた。

それを教えてあげたら「勉強になった!ありがとう!!」って言ってもらえてめっちゃ嬉しかったので、記念にまとめておく。笑

目次

前提状況

実際の状況を再現した図が以下の通り。

やりたかったことはクリックした時にその要素のクラス名を取得したかった。
子要素をクリックした場合は、子要素のクラス名(childClassName)ではなく親要素のクラス名(parentClassName)を取得したい。

これのソースコードを以下のような感じで書いていた。

const clickHandler = (event) => {
  const className = event.target.className;
  console.log(className);
};

return (
  <div className="parentClassName" onClick={clickHandler}>
    <p>親要素</p>
    <div className="childClassName">子要素</div>
  </div>
);

クリックイベントのonClickは親要素に記述している。クリックされた時にclickHandlerにeventが渡されるので、それをevent.target.className で取得している。

この時子要素をクリックするとchildClassNameが取得される。

後にも記述するが、どうやらイベントはDOMを横断するらしい。。。

欲しいのは君じゃない。
君じゃ話にならないんだ。

保護者を呼んできなさい。

stopPropagationを使えばいい?

こういうクリックイベント時に別のDOM要素をクリックしたことになってしまうケースに対しては、stopPropagationが使えたよなーと思い、以下のようにしてみた。

const clickHandler = (event) => {
  event.stopPropagation();

  const className = event.target.className;
  console.log(className);
};

return (
  <div className="parentClassName" onClick={clickHandler}>
    <p>親要素</p>
    <div className="childClassName">子要素</div>
  </div>
);

しかし、全く意味はなく結果は同じ。

調べてみるとstopPropagationは子要素がクリックされた時に親要素のクリックイベントを発火させないようにするために使うものであるらしい。

ここからはちょっと脱線。
結論、stopPropagationでは目的を果たせなかった。
以下ではstopPropagationをどういう場合に使うかを説明。

たとえば以下のように子要素にもクリックイベントが設定されていた場合に子要素をクリックした場合の挙動を見てみる。

const clickHandler = (event) => {
  const className = event.target.className;
  console.log(className);
};

return (
  <div className="parentClassName" onClick={clickHandler}>
    <p>親要素</p>
    <div className="childClassName" onClick={clickHandler}>子要素</div>
  </div>
);

実際の挙動がこちら。

1回クリックしただけなのに2回呼び出されてるー!
これは子要素のクリックイベントと親要素のクリックイベントがどちらも発火するから。

これをstopPropagationを使ってみる。

const clickHandler = (event) => {
  event.stopPropagation();

  const className = event.target.className;
  console.log(className);
};

return (
  <div className="parentClassName" onClick={clickHandler}>
    <p>親要素</p>
    <div className="childClassName" onClick={clickHandler}>子要素</div>
  </div>
);

すると見事親要素のクリックイベントの発火を防ぐことができた。

stopPropagationはこのように使う。

だいぶ脱線してstopPropagationの使い方をまとめたが、結局この方法では子要素をクリックした時に親要素のクラス名を取得するという目的を達成できていない。

実際の解決方法

じゃあどうやって解決したのか。

色々ググったが解決方法はとてもシンプルだった。
以下のようにすれば解決した。

const clickHandler = (event) => {
  const className = event.currentTarget.className;
  console.log(className);
};

return (
  <div className="parentClassName" onClick={clickHandler}>
    <p>親要素</p>
    <div className="childClassName">子要素</div>
  </div>
);

targetをcurrentTargetに変更しただけ。たったこれだけ。

どうやらイベントはDOMを横断するらしい。
targetの場合は必ずしもクリックイベントを記述したDOM要素が特定されるわけではない。
それがcurrentTargetを使うとそのクリックイベントを記述したDOM要素を参照してくれる。

イベントは DOM を横断するので、イベントの現在のターゲットを識別します。イベントが発生した要素を特定する event.target とは対照的に、常にイベントハンドラがアタッチされた要素を参照します。

これによって子要素をクリックした場合でも、クリックイベントが設定されている親要素を参照して、無事親要素のクラス名を取得することができた。

おわりに

親要素にも子要素にもクリックイベントを設定している場合はtargetやcurrentTarget、stopPropagationを使うと上手くできそうな気がする。

DOM操作は普段やらないから知らないことたくさんありそう。

参考

MDN Web Docs
Event: currentTarget プロパティ - Web API | MDN currentTarget は Event インターフェイスの読み取り専用プロパティで、イベントが DOM を走査する際の、イベントの現在のターゲットを特定します。これは常にイベントハン...
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次