이벤트 버블링과 캡처링 완전 이해하기
브라우저에서 이벤트는 단순히 한 요소에서만 끝나지 않는다.
사용자가 어떤 버튼을 클릭하면, 그 클릭 이벤트는 DOM 트리 전체를 따라 전파(propagation) 되며 여러 단계의 흐름을 거친다.
이벤트 전파는 크게 두 가지 흐름으로 나뉜다 — 버블링(Bubbling) 과 캡처링(Capturing).
1. 버블링 (Bubbling)
이벤트 버블링은 가장 안쪽의 요소에서 시작해 바깥쪽 부모 요소로 전파되는 현상이다.
즉, 특정 요소에 이벤트가 발생하면 해당 요소의 핸들러가 실행된 후,
그 이벤트는 부모 → 조상 순으로 계속 전파되며 각각의 핸들러가 차례로 실행된다.
이때 이벤트가 시작된 가장 안쪽의 요소를 타깃(target) 이라 하며, event.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> 요소를 클릭하면 아래 순서로 이벤트가 전파된다.
- <p> 요소의 핸들러 실행
- 부모 <div> 핸들러 실행
- 그 위의 <form> 핸들러 실행
- 마지막으로 document까지 도달
이처럼 “아래에서 위로” 올라가는 방식이 버블링 단계다.
이름 그대로, 물속의 거품이 위로 올라가는 모습과 닮았다.
버블링 중단하기
이벤트가 부모로 전파되는 걸 막고 싶다면 다음 메서드를 사용할 수 있다.
- event.stopPropagation() : 이벤트가 상위 요소로 전파되지 않음
- event.stopImmediatePropagation() : 같은 요소 내의 다른 이벤트 핸들러도 실행되지 않음
<body onclick="alert('버블링은 여기까지 도달하지 못합니다.')">
<button onclick="event.stopPropagation()">클릭해 주세요</button>
</body>
하지만 이 방법은 남용을 피하는 것이 좋다.
지금은 필요 없어 보여도, 나중에 상위 요소에서 이벤트를 처리해야 할 상황이 생길 수 있기 때문이다.
2. 캡처링 (Capturing)
이벤트는 사실 버블링 전에 ‘캡처링 단계’ 를 거친다.
표준 DOM 이벤트 모델에서는 이벤트 전파가 아래 순서로 진행된다.
- 캡처링 단계 – 이벤트가 상위 요소에서 타깃 요소로 전달되는 단계
- 타깃 단계 – 이벤트가 실제 타깃 요소에 도달
- 버블링 단계 – 타깃에서 다시 상위 요소로 전파되는 단계
즉, 캡처링은 “위에서 아래로”, 버블링은 “아래에서 위로” 향한다.
1. 캡처링 이벤트 핸들러 등록하기
기본적으로 addEventListener()는 버블링 단계에서만 작동한다.
하지만 아래처럼 옵션을 { capture: true }로 설정하면 캡처링 단계에서 실행할 수 있다.
document.body.addEventListener(
'click',
() => console.log('캡처링: body 클릭 감지'),
{ capture: true }
);
이렇게 하면 이벤트가 타깃 요소에 도달하기 전에, 상위 요소(body)가 먼저 이벤트를 “가로채” 처리한다.
2. 캡처링이 유용한 상황
1. 이벤트 우선 제어
특정 요소보다 상위 요소가 먼저 이벤트를 감지해야 할 때
예: 하위 버튼 클릭 전에 사용자 권한을 확인하거나 로깅할 때
function PermissionExample() {
const handleClickCapture = (e: React.MouseEvent) => {
console.log("👮 권한 확인 중...");
const hasPermission = false;
if (!hasPermission) {
e.stopPropagation(); // 하위 버튼 이벤트 실행 방지
alert("권한이 없습니다!");
}
};
const handleClick = () => {
console.log("✅ 버튼 동작 수행");
};
return (
<div
onClickCapture={handleClickCapture}
className="p-6 border rounded-md bg-gray-50"
>
<button onClick={handleClick} className="bg-blue-500 text-white p-2 rounded">
민감한 동작 실행
</button>
</div>
);
}
사용자가 버튼을 클릭하면, 상위 요소의 onClickCapture가 먼저 실행되는데,
권한이 없으면 이벤트 버블링을 막고, 하위 버튼 로직(handleClick)은 실행되지 않는다.
2. 보안 / 무결성 제어
하위 요소의 동작을 사전에 차단하거나 허용 여부를 판단할 때
function IntegrityExample() {
const handleCapture = (e: React.MouseEvent) => {
const isSafe = Math.random() > 0.5; // 임의의 조건
console.log("🧩 데이터 무결성 검사 중...");
if (!isSafe) {
e.stopPropagation();
alert("데이터 무결성 검증 실패! 실행이 중단되었습니다.");
}
};
const handleClick = () => {
console.log("데이터 업데이트 실행 중...");
};
return (
<div
onClickCapture={handleCapture}
className="p-6 border rounded-md bg-white"
>
<button onClick={handleClick} className="bg-green-500 text-white p-2 rounded">
데이터 업데이트
</button>
</div>
);
}
캡처링 단계에서 데이터를 미리 검사하여, 문제가 감지되면 이벤트 전파를 막아 하위 로직이 실행되지 않도록 한다.
3. 이벤트 흐름 분석
이벤트가 어떤 경로로 전파되는지 디버깅할 때 유용(캡처링 단계부터 추적 가능)
function EventFlowExample() {
const handleOuterCapture = () => console.log("🔵 Outer(Capture)");
const handleOuter = () => console.log("🔵 Outer(Bubble)");
const handleInnerCapture = () => console.log("🟢 Inner(Capture)");
const handleInner = () => console.log("🟢 Inner(Bubble)");
return (
<div
onClickCapture={handleOuterCapture}
onClick={handleOuter}
className="p-6 border rounded-md bg-gray-100"
>
<button
onClickCapture={handleInnerCapture}
onClick={handleInner}
className="bg-purple-500 text-white p-2 rounded"
>
클릭!
</button>
</div>
);
}
콘솔 출력 순서 → Outer(Capture) → Inner(Capture) → Inner(Bubble) → Outer(Bubble)
이를 통해 캡처링 → 버블링의 흐름을 한눈에 확인할 수 있다.
4. 복잡한 UI 제어
여러 이벤트가 동시에 발생할 때, 캡처링으로 우선순위 조정이 가능하다.
예를 들어, 카드 안의 버튼 클릭 시 카드 클릭 이벤트와 버튼 클릭 이벤트가 중복 실행되는 걸 방지해야 할 때 유용합니다.
function ComplexUIExample() {
const handleCardCapture = () => {
console.log("🟩 카드의 캡처링 단계에서 우선 제어");
};
const handleCardClick = () => {
console.log("🟩 카드 클릭 이벤트 (버블 단계)");
};
const handleButtonClick = (e: React.MouseEvent) => {
e.stopPropagation(); // 카드 클릭 방지
console.log("🟦 버튼 클릭");
};
return (
<div
onClickCapture={handleCardCapture}
onClick={handleCardClick}
className="p-6 border rounded-md bg-gray-50 cursor-pointer"
>
<h2 className="font-semibold mb-2">카드 타이틀</h2>
<button
onClick={handleButtonClick}
className="bg-pink-500 text-white p-2 rounded"
>
세부 동작
</button>
</div>
);
}
캡처링 단계에서 상위 카드의 제어 로직이 먼저 작동하여, 하위 버튼이 클릭되더라도 카드의 버블 이벤트(onClick)은 실행되지 않도록 제어할 수 있다.
3. event 객체 핵심 정리
| 프로퍼티 | 설명 |
| event.target | 이벤트가 실제 발생한 요소 |
| event.currentTarget | 현재 이벤트 핸들러가 바인딩된 요소 |
| event.eventPhase | 이벤트 흐름 단계 (1=캡처링, 2=타깃, 3=버블링) |
4. 마무리
이벤트 버블링과 캡처링은 단순한 개념 같지만, 이벤트 위임(Event Delegation) 이나 성능 최적화, 보안 제어 등에서도 매우 중요한 개념이다. 버블링은 대부분의 상황에서 자연스럽게 동작하지만, 특정 흐름을 제어해야 할 때는 캡처링이나 stopPropagation() 같은 메서드를 적절히 활용해야 한다.
핵심 포인트:
- 기본은 버블링 중심
- 캡처링은 특별한 상황에서만 사용
- 이벤트 흐름을 이해하면, DOM 이벤트를 훨씬 유연하게 제어할 수 있다.