연휴엔 역시 카공이지~ ☕ 이것도 진짜 FE 근본 질문인 것 같음
(25.01.26. 최초 작성 - 2025.02.14. 추가)
이벤트 전파
DOM 구조에서 이벤트가 발생했을 때, 어떻게 상위 요소와 하위 요소 간에 전달되는지 정의하는 매커니즘
웹 페이지에서 요소 간의 상호작용을 제어하는 데 중요한 역할을 하며, 복잡한 사용자 상호작용 효율적으로 관리.
캡처링 단계 - 타겟 단계 - 버블링 단계로 구성됨.
event.eventPhase를 사용하면, 현재 이벤트 흐름 단계(캡처링:1, 타깃: 2, 버블링:3)를 파악할 수 있음
기본적으로 브라우저는 이벤트 전파를 버블링 단계에서 처리하도록 설계되어 있음
[1단계] 캡처링(Capturing) 단계
이벤트가 DOM 트리 최상위 요소(document)에서 시작해서, 이벤트가 발생한 요소(target)으로 내려가는 단계
이 과정에서 상위 요소에 이벤트 리스너가 있으면 그 순서대로 실행될 수 있음
addEventListner()의 세 번째 인자로 {capture: true}를 전달하면, 캡처링 단계에서도 이벤트를 처리할 수 있음
- capture: true이면, 상위->하위로 내려가는 캡처링 단계에서 이벤트가 실행됨
- capture: false이면, 하위->상위로 전파되는 버블링 단계에서 이벤트가 실행됨
부모 요소에서 자식 요소보다 먼저 이벤트를 처리해야 하는 경우 유용함
페이지 전체적인 트래킹이나 로깅 기능 구현하는 경우
캡처링 단계에서 이벤트를 처리하면 자식 요소에서 추가 이벤트 처리 여부와 관계 없이 부모에서 로직 먼저 실행
자식 요소에서 실제 이벤트 핸들러가 실행되기 전에 부모 쪽 로직이 먼저 실행 됨.
자식 요소에서 실제로 클릭 이벤트를 처리하는 것과 관계 없이, 부모 요소 쪽에서 '사용자가 어디를 클릭했군'을 항상 먼저 기록할 수 있음. 자식 요소의 이벤트 핸들러가 실행되며 다른 로직이 추가로 수행돼도, 트래킹은 이미 끝난 상태.
부모 요소에서 특정 이벤트를 차단하거나 변경해야 하면, 캡처링 단계에서 막을 수 있음
버블링 단계에 설정한다면 부모 요소까지 전파된 후에 차단을 수행해도, 이미 자식 요소에서 핸들러가 이미 실행된 상태라 부모 요소의 개입이 어려울 수 있음.
[2단계] 타겟(Traget) 단계
이벤트가 실제로 발생한 타겟 요소에 도달하는 단계. 타겟 요소에 등록된 이벤트 리스너가 이때 실행 됨
target 요소: 이벤트가 발생한 가장 가장 안쪽 요소. event.target으로 접근할 수 있음
- event.target: 실제 이벤트가 시작된 타겟 요소. 버블링이 진행되어도 변하지 않음
- this(=event. currentTarget): 현재 요소. 현재 실행 중인 핸들러가 참조하는 요소
p => div => form 구조로 되어있는 HTML
이벤트 핸들러는 form.onClick 뿐이지만, div나 p를 클릭해도 이벤트를 catch 해냄.
클릭 이벤트가 어디서 발생했든 상관 없이, <form> 요소까지 이벤트가 버블링되어 핸들러 실행시킴
form.onClick 핸들러 내에 this와 event.target
this(=event.currenTarget): <form> 요소에 있는 핸들러가 동작했으므로, <form>을 가리킴
event.target: 폼 내부에 실제로 클릭한 요소를 가리킴
[3단계] 버블링(Bubbling) 단계
타겟 요소에서 이벤트가 발생한 후, 다시 DOM 트리의 상위 요소들로 이벤트가 전파되어 올라가는 단계
타겟 요소 핸들러 작동 -> 부모 요소 핸들러 작동 -> ... -> 최상단 조상 요소 만날 때까지 반복
대부분의 이벤트는 버블링을 통해 전파됨 (focus 이벤트처럼 버블링 되지 않는 이벤트도 있음)
이벤트 위임 패턴에서 유용함: 동적으로 추가되는 자식 요소를 가진 부모 요소의 경우
부모 요소에 이벤트 리스너를 등록하면, 자식 요소마다 이벤트 리스너를 따로 등록하지 않아도 됨
리스트 부모 요소(ul)에 리스너를 등록하면, 모든 자식 요소(li)의 클릭 이벤트를 처리할 수 있음
li를 클릭하면 li->ul로 이벤트가 전파되어 ul에서 어떤 li(target 인식)가 클릭되었는지 식별함
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
가장 안쪽의 <p>를 클릭해 onClick의 alert('p')가 동작함
바깥의 div의 onClick의 alert('div')가 동작함
바깥의 form의 onClick의 alert('form')이 동작함
document 객체를 만나면 정지함
p => div => form 순서대로 alert 창이 뜸
이벤트가 제일 깊은 곳에 있는 요소에서부터 부모 요소로 거슬러 올라가는 것이 물속 거품 같아서 버블링이라 칭함
이벤트 버블링/캡처링 과정에서 발생가능한 문제란?
1. 이벤트 중첩으로 인해 동일 이벤트가 중복 처리
2. 캡처링 단계에서 불필요한 이벤트가 처리되어 성능 이슈 발생
3. 이벤트 위임 시 문제가 생겨 타겟이 오작동함
버블링 중단으로 문제점 제어하기
📌event.stopPropagation(): 특정 단계에서 이벤트 전파 중단
- 핸들러에게 이벤트를 완전히 처리하고 난 후, 버블링을 중단하도록 명령함.
- 특정 이벤트가 현재 단계에서 멈추고, 더 이상 부모나 자식으로 전파되지 않음
- 요소의 특정 이벤트를 처리하는 핸들러가 여러 개인 상황이라면, 나머지 핸들러의 버블링은 막을 수 없음
-
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)"> <button onclick="event.stopPropagation()">클릭해 주세요.</button> </body>
📌event.stopImmediatePropagation(): 요소에 할당된 모든 핸들러의 버블링을 멈춤
- 요소에 할당된 특정 이벤트를 처리하는 핸들러가 모두 동작하지 않음
📌 preventDefault
- 링크 클릭 시 페이지 이동, 폼 제출 같은 브라우저 기본 동작을 방지할 수 있음
그러나, 꼭 필요한 경우가 아니라면 버블링을 멈추지 않는 것이 좋음.
중첩 메뉴를 만든 경우의 시나리오
1. 각 서브 메뉴에 해당하는 요소에서 클릭 이벤트를 처리하고, 상위 메뉴에서는 작동하지 않게 구현
2. 사람들의 행동 분석을 위해 window 내에서 발생하는 클릭 이벤트 전부를 감지하도록 구현
document.addEventListener('click')
3. stopPropagation으로 버블링이 막힌 부분은 분석이 제대로 되지 않음. dead zone이 됨.
이벤트 버블링을 막는 것보다는, 커스텀 이벤트를 만들어서 버블링을 통제하는 게 나음
출처
[1] 매일메일. 250106. 이벤트 전파(event propagation)에 대해서 설명해주세요. https://maeil-mail.kr
[2] JS 공식 문서. 버블링과 캡처링. https://ko.javascript.info/bubbling-and-capturing
* 공식 독스 내용 꽤 자세해서 읽어보는 것 추천합니다
2025.02.14. 추가
[3] 이벤트 버블링과 캡처링에 대해 설명해주세요. (FE.250214) https://maeil-mail.kr
'개발자 강화 > 프론트엔드' 카테고리의 다른 글
[개발][BFF 도전기] Fastify 서버 - 폴더 구조화 (0) | 2025.02.18 |
---|---|
[개발][BFF 도전기] Next.js 구축, Fastify에 Swagger 안 붙는 문제 해결하기 (1) | 2025.02.16 |
[개발, 매일메일] @tanstack/react-query v4/v5 차이...(suspense, error-boundary 처리) (0) | 2025.02.14 |
[매일메일] 민감한 데이터는 어디에 저장해야 할까? (FE.250211) (0) | 2025.02.11 |
[공부] BFF API란? (1) | 2025.02.10 |