Back to list

#08. Bubbling & Capturing

목차5

Event

React에서, 이벤트는 아래와 같이 사용한다:

<input onChange = { (e) => { console.log(e.target.value) } }

여기서 e는 이벤트를 의미한다.

이때 브라우저마다 정의하는 ‘이벤트 객체’의 형태가 다를 수 있으므로,
실제로 코드에서 사용하는 e는 순수한 DOM 이벤트가 아닌,
React가 브라우저와 무관하게 이벤트를 다룰 수 있도록 한번 감싼 객체(SyntheticEvent)이다.

  • e.target: 실제 시작된 가장 안쪽 요소
  • e.currentTarget: 현재 이 이벤트에 응답하는 요소 (즉, 이 핸들러가 실행되고 있는 요소)
  • e.target.value: input의 현재 값 (ex: input 박스에 입력된 현재 텍스트)
  • e.nativeEvent: 감싸기 전 원본 DOM 이벤트

Event Bubbling & Capturing

브라우저에서 사용자가 특정 요소를 클릭할 때, 클릭 이벤트는 최상단에서 타깃까지를 왕복하는 여정을 거친다.
예를들어 사용자가 브라우저에서 어떤 버튼을 눌렀을 때:

  1. Capture: Window → Document → 조부모 → 부모 → …
  2. Target: 자식 요소(
  3. Bubble: 자식 → 부모 → … → Document → Window

브라우저 DOM의 규칙은, 이벤트 핸들러는 기본적으로 “Bubble” 단계에서 실행되는 것이다.
예를들어 아래와 같은 상황에서,

<div onClick={() => console.log("조부모")}>
  <div onClick={() => console.log("부모")}>
    <button onClick={() => console.log("자식")}>클릭</button>
  </div>
</div>

콘솔에 찍히는 실행 결과는 “자식” → “부모” → “조부모”이다.

stopPropagation

당연히, Target 요소에서만 이벤트 핸들러를 호출하고 상위로의 전파를 막는 방법도 있다:

<div onClick={() => console.log("조부모")}>
  <div onClick={() => console.log("부모")}>
    <button onClick={(e) => { 
      e.stopPropagation();
      console.log("자식"); } }>
        클릭
    </button>
  </div>
</div>

e.stopPropagation()은 자식에 시작된 이벤트는 “여기에서” 버블링을 멈추라는 뜻이다.
따라서 위 시나리오에서는 “자식”까지만 출력된다.

onClickCapture

여기서 만약, 자식들이 뭘 하든 상관없이 위에서 먼저 개입해야하는 상황이 있다면 어떨까?
이때는 onClickCapture를 사용하여, “Capture” 단계에서 이벤트 핸들러를 실행할 수 있다.

function App() {

  const logClick = (e) => {
    console.log("로깅:", e.target);  // 내려가는 길에 먼저
  };

  return (
    <div onClickCapture={logClick}>
      <button onClick={(e) => {
        e.stopPropagation();          // 버블만 막힘
        console.log("버튼 자기 일 처리");
      }}>
        구매
      </button>
    </div>
  );
}

실행하면 “로깅: ~”, “버튼 자기 일 처리” 순으로 출력된다.

vs. Compose의 터치 이벤트 소비 방식

React에서는 자식을 클릭했는데 부모, 부모의 부모, 부모의 부모의 부모… 까지 모두 전파되는 시나리오를 고려해야한다면,
반대로 Jetpack Compose에서는 “자식이 이벤트를 소비해서 감싸고있는 부모는 이벤트를 소비하지 못하는” 경우를 고려해야 한다.

Card(onClick = { println("카드") }) {
    Button(onClick = { println("버튼") }) { Text("클릭") }
}

이 경우, 실제로는 “버튼”만 출력된다 — Card의 println은 무시됨.

링크가 복사되었습니다