티스토리 뷰

프론트엔드

이벤트 전파(Event Propagation)

devhanyoung 2024. 4. 14. 15:05

 

유저와 상호작용하는 웹페이지를 위해 반드시 알아야 하는 개념이 있다.

바로 이벤트이다.

화면 상의 요소를 클릭하거나, 스크롤을 하거나, 값을 입력하는 등의 상호작용은 모두 이벤트를 통해 처리된다.

프론트엔드에서 이벤트는 없어서는 안 될 필수적인 존재다.

 

그런데 이벤트를 공부하다 보면 이벤트의 동작 방식이 생각보다 복잡하다는 사실을 알 수 있다.

이벤트를 잘 다루기 위해서는 이벤트가 어떻게 동작하는지를 이해해야 한다.

이번 포스팅을 통해 이벤트의 동작 방식인 이벤트 전파 및 관련 개념들에 대해 정리하고자 한다.

 

목차는 다음과 같다.

1. 이벤트 전파
  1-1. 이벤트 전파란
  1-2. 이벤트 전파가 존재하는 이유
  1-3. 이벤트 전파 막는 법
2. 캡처링
  2-1. 캡처링이란
  2-2. 활용 방법
  2-3. 예시
3. 버블링
  3-1. 버블링이란
  3-2. 활용 방법
  3-3. 예시
  3-4. 이벤트 위임
4. 자주 쓰이는 이벤트 프로퍼티/메서드

이벤트 전파

이벤트 전파란

HTML 문서는 계층적으로 구성되어 있다. 

요소 안에 또 다른 요소가 존재하는 식으로 말이다.

이러한 계층적 구조에 맞게 이벤트 발생도 연쇄적인 흐름으로 이루어지는데, 이 현상을 이벤트 전파라고 한다.

https://www.w3.org/TR/uievents/#event-flow

이벤트 전파는 1) 캡처링 단계 2) 타겟 단계 3) 버블링 단계로 이루어진다.

1) 캡처링 단계에서는 window에서 발생한 이벤트 아래로 전파되고,

2) 타겟 단계에는 실제 타겟 요소에 이벤트가 전달되며,

3) 버블링 단계에서는 이벤트가 타겟 요소로부터 상위 요소로 전파된다.

 

예를 들어 <td>를 클릭했다면,

1) 캡처링 단계에서 '<td> 클릭 이벤트'가 최상위 조상인 window에서부터 타겟 요소인 <td>까지 전달되고, 

2) 타겟 단계에서 타겟인 <td>에 이벤트가 전달되며,

3) 버블링 단계에서 <td>로부터 상위 요소인 <tr>, <tbody>, <table> ... 로 이벤트가 순차적으로 전달되는 식이다.

 

이벤트 전파가 존재하는 이유

1. 직관에 부합한다

이벤트 전파 개념에 대해 처음 접하면, 왜 이렇게 복잡하게 해놓은 건지 잘 이해되지 않을 수 있다.

하지만 곰곰이 생각해보면 이벤트 전파가 오히려 직관에 부합하는 방식임을 알 수 있다.

이게 정확한 비유인지는 모르겠지만, 배와 배꼽을 떠올려보자. (배를 배꼽의 부모 요소라고 생각하자.)

배꼽을 누르면, 이 누르는 행위는 배꼽에서만 독립적으로 발생했다고 할 수 있을까?

그렇지 않다. 배꼽을 누르면 배가 눌리는 느낌도 함께 들 것이기 때문이다.

이런 점을 생각해보면 이벤트가 부모 요소에게 전파되는 현상이 타당해 보인다.

 

2. 성능 개선에 활용될 수 있다

이벤트 전파를 이용하면 이벤트 처리 관련 성능을 개선할 수 있다.

여러 개의 리스트 아이템들에 클릭 이벤트 리스너를 달아주어야 한다고 해보자.

이벤트 전파가 없었다면 이벤트 리스너를 각각의 아이템에 일일이 달아주어야 했을 것이다.

반면, 이벤트 전파를 이용하면 이벤트 리스너를 아이템들의 부모 요소인 리스트 하나에만 달아주는 방식으로 처리할 수 있다.

 

이벤트 전파 막는 법

이벤트가 전파되는 일을 막고 싶은 경우 event.stopPropagation()을 활용하면 된다.

event.stopPropagation();

// 활용 예시
// 캡처링 전파 중단
someElement.addEventListener("click", (e) => {
  // 🛑 캡처링 전파 중단
  e.stopPropagation();
  // handle event
}, true)


// 버블링 전파 중단
someElement.addEventListener("click", (e) => {
  // 🛑 버블링 전파 중단
  e.stopPropagation();
  // handle event
})

일단 event.stopPropagation()을 통해 이벤트 전파를 막을 수 있다는 사실 정도만 알고 넘어가자.


캡처링

캡처링이란

앞서 살펴 보았듯, 캡처링이벤트가 최상위 조상인 window로부터 시작해 아래로 전파되는 현상을 말한다.

캡처(capture)는 '포획하다'라는 의미를 가진 단어이다.

그물로 물고기를 잡을 때 공간을 좁혀나가며 포획하는 것처럼, 이벤트가 상위 요소에서 타겟 요소 쪽으로 점점 좁혀진다는 의미를 담지 않았을까 싶다.

 

활용 방법

// addEventListener Syntax
addEventListener(type, listener, options)
addEventListener(type, listener, useCapture)

// 💡 캡처링 단계 이벤트 처리 on
addEventListener(type, listener, { capture: true });
addEventListener(type, listener, true);

// ❌ 인라인 이벤트 핸들러 (onclick, onload 등)로는 캡처링을 활용할 수 없다

 

캡처링 단계 이벤트 처리를 활성화하기 위해서는, addEventListener 메서드의 세번째 인자에 'capture: true'를 포함한 객체를 넘기거나, 불리언 true를 넘기면 된다.

 

TMI이지만, 사실 이벤트 캡처링 단계는 useCapture 옵션을 어떻게 설정하든 간에 무조건 이뤄진다.

이벤트 흐름 자체를 우리가 제어할 수는 없다는 의미이다.

다만 캡처링 단계에서 해당 이벤트가 처리되게 할지 아닐지 결정할 수 있을 뿐이다.

 

예시

사실 캡처링에 관해 구글링해보거나 실무자 리뷰어 분의 이야기를 들어보면, 실무에서 캡처링 단계를 이용하는 경우는 흔하지 않다고 한다.

그래도 알아두어서 나쁠 건 없으니, 예시를 간단하게나마 알아보고 넘어가자.

예시 코드는 인파님의 블로그 에서 빌려왔다.

See the Pen Untitled by 박한영(Ryan) (@Parkhanyoung) on CodePen.

가장 안쪽에 위치한 child(빨간색 요소)를 클릭하면,  ancestor > parent > child 순으로 이벤트가 처리된다는 사실을 확인할 수 있다.

캡처링은 이 예시처럼 이벤트가 상위에서 하위로 전파되는 현상을 말한다.


버블링

버블링이란

버블링이벤트가 타겟 요소로부터 상위로 전파되는 현상을 말한다.

이벤트가 하위 요소로부터 상위로 전파되는 모습이 물 속 거품이 수면 쪽으로 점점 상승하는 모습과 닮아 버블링이라 이름이 붙여졌다고 한다. 

 

활용 방법

이벤트 버블링은 디폴트 옵션이기 때문에 별도의 설정없이 활용할 수 있다.

혹은 다음과 같은 방식으로 활용할 수 있다.

// 💡 버블링 단계 이벤트 처리 on
addEventListener(type, listener, { capture: false });
addEventListener(type, listener, false);

// ✅ 인라인 이벤트 핸들러에서도 별도의 설정 없이 버블링 단계 이벤트를 처리할 수 있다

단, focus, blur와 같이 이벤트 버블링이 발생하지 않는 이벤트도 존재한다는 사실에 유의하자.

MDN 문서에 The event does not bubble이라고 나와있다.

 

예시

See the Pen 이벤트 버블링 예시 by 박한영(Ryan) (@Parkhanyoung) on CodePen.

가장 안쪽에 위치한 child(빨간색 요소)를 클릭하면,  child > parent > ancestor 순으로 이벤트가 처리된다는 사실을 확인할 수 있다.

버블링은 이 예시처럼 이벤트가 하위(타겟)에서 상위로 전파되는 현상을 말한다.

 

이벤트 위임(Event Delegation)

이벤트 위임은 이벤트 핸들러를 부모 요소에만 할당하여 해당 요소의 하위 요소에서 발생하는 이벤트를 처리하는 방법이다.

여러 요소의 이벤트를 핸들링해야 하는 상황에서 각각의 요소에 일일이 이벤트 리스너를 달아줄 필요 없이 부모 요소에만 달아주면 되므로 성능 상 이점이 있다.

가장 대표적인 예시를 하나 살펴보자.

See the Pen 이벤트 위임 예시 by 박한영(Ryan) (@Parkhanyoung) on CodePen.

이 예시에서 <li>들의 부모 요소인 <ul>에만 이벤트 핸들러를 달아준 것에 주목하자.

모든 <li>에 각각 이벤트 핸들러를 달아줄 필요 없이, 부모 요소인 <ul>에서 버블링을 이용해 이벤트를 처리해주었다.

자식 요소들의 이벤트 처리 책임을 부모 요소에게 위임했다고 해서, 이를 이벤트 위임이라고 부르는 것이다.

유사한 여러 개의 요소에 동일한 이벤트 핸들러를 부착해야 하는 상황에서는 이벤트 위임을 사용하자.


자주 쓰이는 이벤트 프로퍼티/메서드

 

끝으로 자주 활용되는 이벤트 객체의 프로퍼티와 메서드를 정리해보자.

유사해서 헷갈리니 잘 구별할 줄 알아야 한다.

 

target: 이벤트가 발생한 지점에 해당하는 요소를 가리키는 프로퍼티이다. 이벤트가 어떤 요소에서 발생했는지 알아낼 때 사용한다.

currentTarget: 이벤트를 처리하고 있는 이벤트 핸들러가 등록된 요소를 가리키는 프로퍼티이다. 만약 부모가 버블링을 이용해 자식의 이벤트를 처리하는 상황이라면, 부모의 이벤트 핸들러에서 event.currentTarget은 그 부모 요소가 된다.
preventDefault(): 이벤트의 기본 동작을 취소하는 메서드이다. 예를 들어 <form> 요소 submit 이벤트의 기본 동작은 폼을 전송하고 새로고침하는 것인데, 이 메서드를 호출하면 이 기본 동작을 취소할 수 있다.

stopPropagation(): 앞서 살펴보았듯, 이벤트의 전파를 중단하는 메서드이다. 버블링 핸들러라면 더 이상 상위 요소로 버블링되지 않게 막고, 캡처링 핸들러라면 더 이상 하위 요소로 캡처링되지 않게 막는다.

stopImmediatePropagation(): 기본적으로 stopPropagation()과 유사하지만, 현재 이벤트 핸들러 이후의 이벤트 핸들러들도 실행되지 않게 막는다. 만약 하나의 요소에 여러 개의 이벤트 핸들러가 등록되어 있는데 이 메서드가 호출된다면, 그 요소에 대한 다른 이벤트 핸들러들도 실행되지 않는다.


짧은 소감

이벤트는 우리의 웹페이지가 유저와 상호작용할 수 있게 만들어주는 아주 중요한 개념이다.

하지만 이벤트 관련해서 시간 내어 공부한 적은 따로 없었다.

필요할 때마다 어쩔 수 없이 주먹구구 식으로 공부한 게 전부였다.

이렇게 이벤트 동작 방식에 대해 큰 틀에서 한 번 정리를 하고 나니, 이벤트가 그렇게까지 어렵지 않게 느껴진다.

이벤트를 효과적으로 처리할 수 있겠다는 자신감이 생긴다. 💪🏻

 

'프론트엔드' 카테고리의 다른 글

DOM, Node, Element  (1) 2024.04.11
옵저버 패턴으로 컴포넌트 상태 변화 감지하기  (1) 2024.04.10