이벤트 처리

박찬욱·2023년 5월 24일
0

Web APIs

목록 보기
10/12
post-thumbnail

이벤트

쉽게 말해서 사용자가 HTML 요소에 일으키는 행위라고 생각하면 된다. 브라우저를 클릭하거나 키보드를 누르거나 페이지를 스크롤하거나 비디오를 실행시키는 등, 여러가지 이벤트가 존재한다. 관련 설명은 MDN 사이트에 자세히 나와있으니 참고하면 되겠다.

이벤트 핸들러

우리는 이렇게 브라우저에서 발생하는 이벤트를 제어하고 다룰 수 있다. 이벤트가 발생할 때 처리하고 싶은 로직을 작성할 수 있고, 이벤트를 제거하거나 직접 만들어서 발생시킬 수도 있다. 이벤트를 다룰 수 있는 메서드들이 바로 이벤트 핸들러이다.

  • addEventListener
    • 요소에 이벤트를 등록하는 핸들러
  • removeEventListener
    • 요소에 이벤트를 제거하는 핸들러
  • dispatchEvent
    • 이벤트를 직접 만들어 실행시키는 핸들러
    • 이벤트를 new Event 로 생성하고 dispatchEvent 를 이용해서 이벤트를 실행시킬 수 있다. 커스텀 이벤트가 필요할 때 사용한다.

이벤트 버블링

이벤트는 캡처링버블링 의 단계가 있다. 캡처링 단계에서는 이벤트를 처리하는 경우가 거의 없기 때문에 버블링 에 집중하겠다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <script defer src="./main.js"></script>
    <title>이벤트 버블링</title>
  </head>
  <body>
    <div id="ancestor">
      <div id="parent">
        <div id="child">
          <button id="btn">click me!</button>
        </div>
      </div>
    </div>
  </body>
</html>

const ancestor = document.querySelector("#ancestor");
const parent = document.querySelector("#parent");
const child = document.querySelector("#child");
const btn = document.querySelector("#btn");

ancestor.addEventListener("click", (e) => {
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

parent.addEventListener("click", (e) => {
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

child.addEventListener("click", (e) => {
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

btn.addEventListener("click", (e) => {
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

먼저 각 요소에 이벤트 핸들러를 등록해서 currentTargettarget 을 출력해보았다.

🤔 currentTarget / target

currentTarget 은 이벤트 핸들러가 연결된 요소이고 target 은 이벤트가 시작된 위치이다.


⬇️ 출력 결과

버튼을 클릭했고 출력 결과가 다음과 같다. 먼저 버튼의 currentTargettarget 은 일치하다. 왜냐하면 이벤트 핸들러가 등록된 것은 버튼요소이고 이벤트가 발생한 시작점 또한 버튼이기 때문이다.
그런데 버튼을 클릭했을 뿐인데 child, parent, ancestor 요소 또한 이벤트가 발생하였고 이들의 currentTargettarget 은 서로 다르다. 왜냐하면 이벤트는 버튼요소에서 시작되었지만 이벤트 핸들러는 각각의 요소마다 연결이 되어있기 때문이다. 즉, 버튼에서 이벤트가 발생되어 상위 요소로 버블링 되어 각각의 요소에 연결되어있는 이벤트 핸들러의 콜백함수를 호출하였다.


버블링의 장점

버블링은 이벤트 위임 을 사용할 수 있다. 즉, 자식 요소 각각에 이벤트를 등록하지 않고 부모 요소에만 이벤트를 등록해도 자식 요소에서 발생하는 이벤트가 버블링되어 부모 요소로 전파되기 때문에 이벤트 등록 코드를 줄일 수 있다.


❌ stopPropagation / stopImmediatePropagation

이벤트 버블링을 막는 메서드들이다. 요소에 stopPropagation 을 사용하면 부모 요소로 버블링되는 이벤트를 막는다.
형제 노드의 경우에는 이벤트를 등록한 순서대로 이벤트 핸들러가 작동한다. 즉, button 1을 먼저 이벤트 핸들러를 등록하고 button 2를 나중에 등록했을 때, button 2에 stopPropagation 을 사용해도 button 1에서 등록한 이벤트 핸들러가 호출된다. 이조차도 막기 위해서 사용하는 것이 stopImmediatePropagation 이다.

이 두 메서드는 사용하지 않는 것이 좋다. 왜냐하면 자식 요소에 이벤트가 발생하여 이를 부모 요소에서 처리해서 작업하는 로직이 필요한 경우에 매우 치명적이기 때문이다. 그렇다면 이 두 메서드를 사용하지 않고 이벤트 버블링을 막기 위해서는 어떻게 해야할까?


currentTarget 과 target 을 이용하자

내가 관심있는, 이벤트를 처리하고 싶은 요소만 이벤트 핸들러를 이용해 처리하고 싶은 경우에 currentTargettarget 을 이용하면 된다.

const ancestor = document.querySelector("#ancestor");
const parent = document.querySelector("#parent");
const child = document.querySelector("#child");
const btn = document.querySelector("#btn");

ancestor.addEventListener("click", (e) => {
  if (e.currentTarget !== e.target) {
    return;
  }
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

parent.addEventListener("click", (e) => {
  if (e.currentTarget !== e.target) {
    return;
  }
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

child.addEventListener("click", (e) => {
  if (e.currentTarget !== e.target) {
    return;
  }
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

btn.addEventListener("click", (e) => {
  console.log("currentTarget", e.currentTarget, "target", e.target);
});

만일 이벤트가 발생한 타겟이벤트 핸들러가 등록된 요소가 다르다면 해당 요소에서 이벤트가 발생한 것이 아니다. 이렇게 하면 내가 관심있는, 현재 코드에서는 버튼을 클릭했을 때 등록한 이벤트 핸들러의 콜백함수만 호출되고 나머지 부모 요소의 이벤트 핸들러의 콜백함수는 호출되지 않는다. 이렇게 내가 원하는 요소의 이벤트 핸들러만 사용할 수 있게 된다.

profile
대체불가능한 사람이다

0개의 댓글