본문 바로가기
부트캠프

멋쟁이사자처럼 프론트엔드 부트캠프 14기 - Day 44 (이벤트 전파, 이벤트 트리거 시퀀스, 이벤트 전파 방지, 이벤트 위임, 이벤트 리스너 제거)

by 나른한_꼬리_ 2025. 6. 30.
반응형

멋쟁이사자처럼 프론트엔드 부트캠프 14기 - Day 44 (이벤트 전파, 이벤트 트리거 시퀀스, 이벤트 전파 방지, 이벤트 위임, 이벤트 리스너 제거)

 

목차

 

이벤트 전파(Event Propagation)

이벤트가 발생하면 다음의 세 가지 단계가 순차적으로 발생하는데, 이것을 이벤트 전파(Event Propagation)라고 한다.

  1. 캡처링 단계(Capturing phase)
  2. 타겟 단계(Target phase)
  3. 버블링 단계(Bubbling phase)

 

1. 캡처링 단계(Capturing Phase)

JavaScript는 window → document → event target에 도달할 때까지 이벤트를 전파한다. 이벤트 리스너의 콜백에 세 번째 인수로 불리언 값을 제공하면 캡처링 단계를 수신할 수 있다.

document.addEventListener (
  'event type',
  callback,
  useCapture,
)

useCapture 매개변수의 기본 값은 false다. 기본적으로 캡처링 단계를 수신하지 않는데, 필요하다면 true를 설정해서 캡처링 단계를 수신한다.

아래의 중첩된 div 박스 3개를 통해 캡처링 단계가 어떻게 작동하는지 확인해 보자.

<div class="box box1">
  <span>박스 1</span>
  <div class="box box2">
    <span>박스 2</span>
    <div class="box box3">
      <span>박스 3</span>
    </div>
  </div>
</div>

 

각 박스에 클릭 이벤트를 추가해 보자. 이벤트 리스너의 콜백은 이벤트 객체를 통해 이벤트 단계(e.eventPhase)현재 대상(e.currentTarget)이 무엇인지 콘솔 패널에 출력한다.

const boxes = document.querySelectorAll('.box')

boxes.forEach((box) => {
  box.addEventListener(
    'click',
    (e) => {
      console.log(e.eventPhase, e.currentTarget)
    },
    true,
  )
})

 

이벤트 단계(e.eventPhase) 속성은 현재 단계가 무엇인지 알려준다.

  1. 반환 값 1 = 이벤트 캡처링 단계
  2. 반환 값 2 = 이벤트 타겟 단계
  3. 반환 값 3 = 이벤트 버블링 단계

박스 3을 클릭하면 부모에서 자식으로 위에서 아래로 내려온다.

 

2. 타겟 단계(Target Phase)

타겟 단계는 useCapture 설정을 무시한다. 즉, useCapture 설정과 상관 없이 이벤트를 발생시킨 요소에 도달하여 해당 요소에 연결된 모든 이벤트를 트리거한다는 뜻이다.

const box3 = document.querySelector('.box3')

// 이벤트 캡처링 단계 수신
box3.addEventListener(
  'click', listener, true
)

// 이벤트 버블링 단계 수신
box3.addEventListener(
  'click', listener
)

 

 

3. 버블링 단계(Bubbling Phase)

useCapture 설정이 없으면 이벤트는 버블링 단계로 작동한다. 버블링 단계는 이벤트를 발생시킨 요소에서 시작해 Window까지 모든 요소에 이벤트를 전파한다.

const boxes = document.querySelectorAll('.box')

boxes.forEach((box) =>
  box.addEventListener(
    'click', 
    (e) => {
      console.log(
        e.eventPhase, 
        e.currentTarget
       )
    }
  ),
)

 

버블링 단계에 따라 이벤트가 전파된 경우 다음 순서로 트리거된다. 밑에서 위로 자식에서 부모로 올라간다.

  1. 박스 3, 타겟 단계(2)
  2. 박스 2, 버블링 단계(3)
  3. 박스 1, 버블링 단계(3)

캡처링은 잘 사용되지 않는다. 주로 버블링을 사용한다.

 

이벤트 트리거 시퀀스

두 개의 이벤트 리스너가 동일 요소에 추가됐을 때는 추가된 순서로 리스너를 실행한다.

const button = document.querySelector('button')

button.addEventListener('click', () => {
  console.log('첫 번째 이벤트 트리거')
})

button.addEventListener('click', () => {
  console.log('두 번째 이벤트 트리거')
})

// '첫 번째 이벤트 트리거'
// '두 번째 이벤트 트리거'

 

 

이벤트 전파 방지

이벤트가 버블링 단계에서 전파되는 것을 방지해야 할 때가 있을 수 있다. 그럴 때는 stopPropagation() 또는 stopImmediatePropagation() 메서드를 사용한다.

  • stopPropagation(): 이벤트가 상위로 전파되는 것 방지
  • stopImmediatePropagation(): 이벤트가 상위로 전파되는 것을 방지하고, 후속 이벤트가 발생하는 것도 방지
const box2 = document.querySelector('.box2')
const box3 = document.querySelector('.box3')

box2.addEventListener('click', () => {
  console.log('박스 2 클릭!')
})

box3.addEventListener('click', (e) => {
  e.stopPropagation()
  console.log('박스 3 클릭!')
})

// 박스 3에서 이벤트가 발생했을 때 전파 방지를 했기 때문에
// 박스 2는 이벤트가 발생하지 않는다.

 

동일 요소에 연결된 두 개의 이벤트 리스너 콜백 함수 중 나중에 위치한 것이 실행되는 것을 방지하려면 stopImmediatePropagation() 메서드를 사용한다.

box3.addEventListener('click', (e) => {
  // 동일 요소로 이벤트 전파되는 것을 즉시 멈춤!
  e.stopImmediatePropagation()
  console.log('박스 3-1 클릭!')
})

box3.addEventListener('click', (e) => {
  // 이벤트 전파가 중지되어 실행되지 않음
  console.log('박스 3-2 클릭!')
})

 

이벤트 위임(Event Delegation)

<ul class="link-list">
  <li><a href="/news">📢 최신 뉴스 보기</a></li>
  <li><a href="/tutorials">📘 튜토리얼 모음</a></li>
  <li><a href="/community">💬 커뮤니티 참여</a></li>
  <li><a href="/resources">📁 개발 자료실</a></li>
  <li><a href="/contact">📮 문의하기</a></li>
</ul>

위와 같이 많은 링크 목록에 이벤트 리스너를 생성해야 할 때 하나씩 다 생성하려고 하면 코드도 너무 길어지고 복잡하고, 무엇보다 귀찮을 것이다. 이때 이벤트 위임을 사용한다.

 

이벤트 위임 방법은 이벤트 전파를 활용한다. 작동 방식은 다음과 같다. (버블링 단계의 이벤트에서만 작동)

  1. 상위 요소에 하나의 이벤트 리스너 추가
  2. 상위 요소는 하위 요소의 모든 이벤트 수신
const linkList = document.querySelector('.link-list')

linkList.addEventListener('click', (e) => {
  // 이벤트 타겟 설정
  const target = e.target
  
  // 타겟 매칭 검사
  if (target.matches('a')) {
    e.preventDefault()
    console.log('링크 요소를 클릭했습니다.')
  }
})

 

이벤트를 발생시키는 요소를 이벤트 타겟이라고 하며, 이벤트 객체의 target 속성으로 접근할 수 있다. 이벤트 위임 방법은 이벤트를 수신하는 요소에서 발생하는 모든 이벤트에 반응하기 때문에 현재 이벤트 리스너가 추가된 요소의 모든 하위 요소는 이벤트 타겟이된다. 따라서, 정확히 이벤트가 발생된 대상이 맞는지 검사해야 한다. 정확히 매칭되는지 검사할 때는 matches() 메서드를 사용한다.

a 요소나 button 요소 안에 다른 요소가 중첩되어있다면, 그 안에 있는 요소에서 이벤트가 발생할 것이다. 아래와 같은 구조를 살펴보자.

<ul class="link-list">
  <li>
    <a href="/news">
      <svg> <!-- ... -->
      </svg>
      <span>최신 뉴스 보기</span>
    </a>
  </li>
  <!-- ... -->
</ul>

 

이런 상황에서 항상 a 요소를 찾는 방법은 다음과 같다.

  • 이벤트 타겟 요소에 pointer-events: none CSS 스타일을 할당해서 마우스 이벤트에 반응하지 않도록 설정
  • closest() 메서드 사용

 

이벤트 리스너 제거

이벤트 리스너는 DOM에 한 번 추가되면 영구적으로 남아 있기 때문에 더 필요하지 않을 때, 제거하는 것이 좋다. 이벤트 리스너를 제거할 때는 removeEventListener() 메서드를 사용하고, 리스너(콜백 함수)는 이벤트 리스너를 추가할 때 사용한 것과 동일한 참조를 가리켜야 한다. 이벤트 리스너를 생성할 때 리스너를 분리하면 편리한 이유가 여기에 있다.

const button = document.querySelector('button')
// 콜백 분리
const handleClick = (e) => {
  console.log('버튼 클릭!')
}
// 이벤트 리스너 추가
button.addEventListener('click', handleClick)
// 이벤트 리스너 제거
button.removeEventListener('click', handleClick)

이벤트 리스너가 한 번만 작동하게 하려면 다음과 같이 추가된 이벤트가 트리거 됐을 때 이벤트 리스너를 제거하거나, once 옵션true로 설정하면 된다.

const button = document.querySelector('button')

const handleOnceClick = (e) => {
  console.log('버튼 클릭!')
  // 이벤트 리스너 바로 제거
  e.currentTarget.removeEventListener('click', handleOnceClick)
}

button.addEventListener('click', handleOnceClick)

 

once 옵션을 true로 설정하는 방법

const button = document.querySelector('button')

button.addEventListener(
  'click', 
  (e) => { console.log('버튼 클릭!') }, 
  { once: true }
)

 

오늘 하루를 돌아보며

드디어 이벤트 버블링에 대해 배웠다. 지난 UI 프로젝트를 진행하면서 설명만 대충 들었는데 어떻게 작동하는지 자세히 배우니까 자바스크립트가 얼마나 유용한 언어인지 알게 되었다. 제대로 배우면 잘 활용할 수 있을 것 같다. 그리고 잘 활용하려면 그만큼 많이 써보면서 익혀야 한다!

 

어떻게 하든 연습이 답이다! 많이 해보는 게 답이다! 눈으로만 보거나 중얼거리는 걸로는 머리에 들어오지도 않고 필요할 때 떠오르지도 않는다. 직접 써보고 만들어 보고 사용해 봐야 한다. 2번 이상 해보는 게 반복이고 복습이다. 한 번만 해보고 마는 거는 의미가 없다. 수업 시간에 해봤던 실습 내용을 주말을 이용해서 다시 만들어 봐야겠다!

 

반응형