이벤트 캡처링과 버블링 / target과 currentTarget 비교

선정·2023년 2월 26일
0

이벤트와 이벤트 핸들러

  • 이벤트 핸들러 : 특정 요소에서 지정된 이벤트가 발생 했을 때 해당 이벤트를 처리하는 함수로, 연결된 이벤트 핸들러는 호출될 때 자동으로 Event 객체를 첫 번째 인자로 전달 받는다.

예시 )

import { useState } from "react"

function App() {
  const [value, setValue] = setState("")
  
  const handleChange = (event) => {
    setValue(event.target.value)
  }
  
  return (
    <div>
      <input type="text" value={value} onChange={handleChange} />
    </div>
  )
}

export default App

이벤트 캡처링과 버블링

부모 요소를 가지고 있는 요소에서 이벤트가 발생한 경우, 캡처링타깃, 버블링이 차례대로 실행된다.

(1) 캡처링 단계 : <td>를 클릭하면 이벤트가 최상위에서 시작해 아래로 전파된다.
(2) 타깃 단계 : 이벤트가 타깃 요소에 도착돼 실행된 후에
(3) 버블링 단계 : 다시 위로 전파된다.

  • 캡처링(capturing) : 최상위 요소에서부터 실제 이벤트가 발생한 요소에 이르기까지 자식 요소들을 타고 내려가 이벤트 핸들러가 있는지 검사하고, 있으면 그 이벤트 핸들러를 호출한다.
  • 버블링(bubbling) : 실제 이벤트가 발생한 요소부터 최상위 요소에 이르기까지 부모 요소들을 타고 올라가 이벤트 핸들러가 있는지 검사하고, 있으면 그 이벤트 핸들러를 호출한다.

몇몇 이벤트를 제외한 거의 모든 이벤트는 버블링된다. 버블링과 달리 캡처링을 이용해야 하는 경우는 흔치 않다.

target 프로퍼티와 currentTarget 프로퍼티

  • target : 이벤트가 실제 발생한 대상으로, 버블링 혹은 캡처링이 진행돼도 변하지 않는다.
  • currentTarget(=this) : 버블링 혹은 캡처링 진행 중 현재 이벤트가 위치하고 있는 대상으로, 현재 실행 중인 핸들러가 할당된 요소를 참조한다.

currentTarget(=this)는 함수(이벤트 핸들러)를 호출하는 객체이다. 위와 같은 차이로 currentTargettarget과 같은 요소를 참조할 수도, 다른 요소를 참조할 수도 있다.

예시 1)

function App() {
  const printLog = (event) => {
    console.log(`currentTarget: ${event.currentTarget.id}, target: ${event.target.id}`)
  }

  return (
    <form id="outer" onClick={printLog}>
      outer
      <div id="inner" onClick={printLog}>
        inner
        <button id="button" style={{ display: "block" }} onClick={printLog}>
          target
        </button>
      </div>
    </form>
  )
}

export default App

button 클릭 시, 이벤트 버블링으로 인해 모든 요소의 클릭 이벤트 리스너에 등록된 printLog 함수가 호출된 것을 볼 수 있다. 이 때, target은 실제 이벤트가 발생한 button이고, currentTargetbutton부터 시작해 차례대로 printLog 함수가 호출된 부모 요소가 된다.

<button> 클릭 했을 때 아래의 순서로 이벤트 버블링이 발생한다.
1. <button>에 할당된 onClick 핸들러가 동작한다.
2. 바깥의 <div>에 할당된 핸들러가 동작한다.
3. 그 바깥의 <form>에 할당된 핸들러가 동작한다.
4. document 객체를 만날 때까지, 각 요소에 할당된 onClick 핸들러가 동작한다.

예시 2)

function Bubbling() {
  const chageBgToYellow = (event) => {
    console.log(
      `currentTarget: ${event.currentTarget.tagName}, target: ${event.target.tagName}`
    );
    event.target.style.backgroundColor = 'yellow';
  };

  return (
    <form onClick={chageBgToYellow}>
      Form
      <div>
        DIV
        <p>P</p>
      </div>
    </form>
  );
}

export default Bubbling;

<p> -> <div> -> <form> 순으로 클릭했을 때 currentTargettarget의 태그명은 위와 순서와 같다.
이벤트 핸들러는 <form>에만 할당돼있지만 이 핸들러에서 <form> 안의 모든 요소에서 발생하는 클릭 이벤트를 ‘잡아내고(catch)’ 있다. 클릭 이벤트가 어디서 발생했든 상관없이 <form> 요소까지 이벤트가 버블링 되어 핸들러를 실행시키기 때문이다.


이벤트 버블링 처리하기

방법 1) stopPropagation() 또는 stopImmediatePropagation() 메서드 이용

function App() {
  const printLog = (event) => {
    event.stopPropagation()
    console.log(`currentTarget: ${event.currentTarget.id}, target: ${event.target.id}`)
  }

  return (
    <form id="outer" onClick={printLog}>
      outer
      <div id="inner" onClick={printLog}>
        inner
        <button id="button" style={{ display: "block" }} onClick={printLog}>
          button
        </button>
      </div>
    </form>
  )
}

export default App

이벤트 핸들러에 event.stopPropagation()를 사용해 이벤트 전파를 막은 상태에서 button 클릭 시, 이전과 달리 부모 요소에서는 console.log가 실행되지 않는다.

하지만 이 방법은 추후에 문제가 될 수 있는 상황을 만들어낼 수 있으므로 꼭 필요한 경우를 제외하곤 버블링을 막으면 안 된다.

문제가 발생할만한 시나리오
1. 중첩 메뉴에서 서브메뉴에 해당하는 요소에서 클릭 이벤트를 처리하도록 하고 상위 메뉴의 클릭 이벤트 핸들러는 동작하지 않도록 stopPropagation을 적용한다.
2. 이용자들이 웹 페이지에서 어디를 클릭했는지 분석하기 위해, window내에서 발생하는 클릭 이벤트 전부를 감지하기 위해 최상위 요소에 클릭 이벤트 핸들러를 연결한다.
3. stopPropagation로 버블링을 막아놓은 영역에선 2의 코드가 동작하지 않게 된다.

preventDefault(), stopPropagation(), stopImmediatePropagation() 비교
https://velog.io/@line_jeong32/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A9%94%EC%84%9C%EB%93%9C

방법 2) target 프로퍼티와 currentTarget 프로퍼티 이용

function App() {
  const printLog = (event) => {
    const {target, currentTarget} = event
    if(target !== currentTarget) return
    console.log(`currentTarget: ${currentTarget.id}, target: ${target.id}`);
  };

  return (
    <form id="outer" onClick={printLog}>
      outer
      <div id="inner" onClick={printLog}>
        inner
        <button id="button" style={{ display: "block" }} onClick={printLog}>
          button
        </button>
      </div>
    </form>
  );
}

export default App;

이벤트 핸들러가 호출 됐을 때 targetcurrentTarget이 일치하지 않을 시, return 시켜버리면 button 클릭 시, targetcurrentTarget이 일치하지 않는 부모 요소에서는 console.log가 실행되지 않는다.

참고 사이트 및 블로그

profile
starter

0개의 댓글