표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계가 있다.
<td>
를 클릭하면
1. 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계),
2. 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계),
3. 다시 위로 전파된다(버블링 단계).
공식적으론 총 3개의 이벤트 흐름이 있지만, 이벤트가 실제 타깃 요소에 전달되는 단계인 ‘타깃 단계’(두 번째 단계)는 별도로 처리되지 않는다. 캡처링과 버블링 단계의 핸들러는 타깃 단계에서 트리거된다.
<div onClick={() => alert('부모 태그')}>
부모태그
<div onClick={() => alert('자식 태그')}>
자식태그
</div>
</div>
부모태그 클릭 시 "부모 태그"
자식태그 클릭 시 "부모 태그" => "자식 태그"
이처럼 자식 태그를 클릭했지만 부모 태그에 할당된 이벤트가 동작하는 것을 이벤트 버블링 이라고 한다.
버블링의 원리
- 한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고,
- 이어서 부모 요소의 핸들러가 동작한다.
- 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복된다.
버블링 이름 뜻
이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 마치 물속 거품(bubble)과 닮았기 때문에 다음과 같이 부른다.
캡처링 단계를 이용해야 하는 경우는 흔치 않고 대부분의 이벤트는 버블링 이벤트이다. (focus 이벤트 제외)
// javascript
elem.addEventListener(..., {capture: true}) // capture 옵션을 true로 설정
elem.addEventListener(..., true) // 생략 가능
// jsx
<div onClickCapture={...} />
event.eventPhase
프로퍼티를 이용하면 현재 발생 중인 이벤트 흐름의 단계를 알 수 있다.
Event.NONE (0)
: 이 이벤트는 현재 처리 중이 아닌 경우
Event.CAPTURING_PHASE (1)
: 캡처링 단계
Event.AT_TARGET (2)
: 타겟 단계
Event.BUBBLING_PHASE (3)
: 버블링 단계
// 버블링
<div className="최상위" onClick={e => console.log(`최상위 ${e.eventPhase}`)}>
최상위 요소
<div className="중위" onClick={e => console.log(`중위 ${e.eventPhase}`)}>
중위 요소
<div className="최하위" onClick={e => console.log(`최하위 ${e.eventPhase}`)}>
최하위 요소
</div>
</div>
</div>
최상위 클릭 시 "최상위 3"
중위 클릭 시 "중위 3" => "최상위 3"
최하위 클릭 시 "최하위 3" => "중위 3" => "최상위 3"
// 캡처링 후에 이어지는 버블링
<div className="최상위" onClickCapture={e => console.log(`최상위 ${e.eventPhase}`)}>
최상위 요소
<div className="중위" onClickCapture={e => console.log(`중위 ${e.eventPhase}`)}>
중위 요소
<div className="최하위" onClickCapture={e => console.log(`최하위 ${e.eventPhase}`)}>
최하위 요소
</div>
</div>
</div>
최상위 클릭 시 "최상위 1"
중위 클릭 시 "최상위 1" => "중위 1"
최하위 클릭 시 "최상위 1" => "중위 1" => "최하위 1"
// 캡처링 후에 이어지는 버블링
<div className="최상위" onClick={e => console.log(`최상위 ${e.eventPhase}`)} onClickCapture={e => console.log(`최상위 ${e.eventPhase}`)}>
최상위 요소
<div className="중위" onClick={e => console.log(`중위 ${e.eventPhase}`)} onClickCapture={e => console.log(`중위 ${e.eventPhase}`)}>
중위 요소
<div className="최하위" onClick={e => console.log(`최하위 ${e.eventPhase}`)} onClickCapture={e => console.log(`최하위 ${e.eventPhase}`)}>
최하위 요소
</div>
</div>
</div>
최상위 클릭 시 "최상위 1" => "최상위 3"
중위 클릭 시 "최상위 1" => "중위 1" => "중위 3" =>"최상위 3"
최하위 클릭 시 "최상위 1" => "중위 1" => "최하위 1" => "최하위 3" => "중위 3" => "최상위 3"
// 캡처링 후에 이어지는 버블링 예시 코드
const stopEvent = (e) => {
e.stopPropagation();
};
<div className="최상위" onClick={e => console.log(`최상위 ${e.eventPhase}`)} onClickCapture={e => console.log(`최상위 ${e.eventPhase}`)}>
최상위 요소
// 여기서 중위 요소의 onClick 또는 onClickCapture 동작 함수를 e.stopPropagation()로 변경
<div className="중위" onClick={stopEvent} onClickCapture={stopEvent}>
중위 요소
<div className="최하위" onClick={e => console.log(`최하위 ${e.eventPhase}`)} onClickCapture={e => console.log(`최하위 ${e.eventPhase}`)}>
최하위 요소
</div>
</div>
</div>
event.stopPropagation()
이 위쪽으로 일어나는 버블링 또는 아래쪽으로 일어나는 캡처링을 막아준다.
마우스/터치 이벤트를 제거하고 싶은 요소에 아래 속성을 추가한다.
pointer-events: none;
/* none: 비활성화 */
/* auto: 활성화 */
/* inherit: 부모 요소로부터 상속 받는다. */
하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식으로 이벤트 버블링을 활용
<div onClick="결제">
<svg>결제 아이콘</svg> // 아이콘을 눌러도 상위 요소의 onClick 동작함.
</div>
ie 낮은 버전에서는 작동하지 않아 캡처링은 실제코드에서 잘 사용되지는 않는다.