본문 바로가기
Javascript/Javascript

이벤트위임, 버블링/캡처링, React17에서의 변경점

by img 2023. 7. 20.

DOM

DOM(Document Object Model)은 HTML, XML등의 문서를 트리 구조로 표현한 것입니다. 이 트리 구조는 요소(element), 속성(attribute), 텍스트(text) 등의 노드(node)로 구성되어 있습니다.

DOM에서의 이벤트 처리는 이벤트 위임, 이벤트 버블링, 이벤트 캡처링 등의 개념을 이용해 구현됩니다.

표준 DOM이벤트에서 정의한 3가지 이벤트 흐름은 다음과 같습니다.

첫번째, 캡처링 단계는 이벤트가 하위 요소로 전파되는 단계 이며, 두번째 타깃 단계는 이벤트가 실제 타깃 요소에 전달되는 단계이고, 세번째는 버블링 단계로 이벤트가 상위 요소로 전파되는 단계 입니다.

이벤트 캡처링부터 알아보겠습니다.

이벤트 캡처링

이벤트 캡처링은 최상위 요소에서 하위 요소로 이벤트가 전파되는 방식으로, 이벤트 실행 시 처음 시작되는 단계이며 자주 사용되는 방식은 아닙니다. 

이벤트 캡처링을 구현하려면, addEventListener 메서드의 세 번째 매개변수에 true를 전달하면 됩니다. 이렇게 하면 이벤트 핸들러가 이벤트 캡처링 단계에서 호출되며, 이후에 이벤트 버블링 단계에서도 호출됩니다.

<body>
	<div id="outer">
	  <div id="inner">
	    <button id="button">Click me</button>
	  </div>
	</div>
</body>
const outer = document.querySelector('#outer');
outer.addEventListener('click', handleClick, true);

이 때, #inner 요소에 클릭 이벤트를 등록하고, 이벤트 캡처링을 사용하여 최상위 요소(body)에서부터 이벤트가 발생하는지를 체크하면, 이벤트 캡처링 단계에서는 다음과 같은 순서로 이벤트가 발생합니다.

  1. body 요소에서 이벤트가 발생하는지 체크
  2. #outer 요소에서 이벤트가 발생하는지 체크
  3. #inner 요소에서 이벤트가 발생하는지 체크

따라서 이벤트가 발생한 요소(#inner)를 제외한 상위 요소에 대해서도 이벤트 핸들러가 실행됩니다. 이벤트 캡처링은 이벤트 버블링과 반대되는 방식입니다.

 

이벤트 버블링

이벤트 버블링은 하위 요소에서 상위 요소로 이벤트가 전파되는 방식을 의미하며, 기본적으로 많이 사용되는 방식입니다.

이벤트 캡처링과 이벤트 버블링은 모두 이벤트 전파 방식에 차이가 있을 뿐, 이벤트 핸들러에서는 이 두 방식을 모두 사용할 수 있습니다.

<div class="outer">
  <div class="inner">
    <button>Click me</button>
  </div>
</div>

위 코드에서는 button 요소를 클릭했을 때 이벤트가 발생합니다. 이벤트 버블링이 적용되면, 클릭 이벤트는 button 요소에서 시작하여 inner 요소, outer 요소, 그리고 최상위 요소까지 전파됩니다. 이벤트가 최상위 요소까지 전파되면, 최상위 요소에 등록된 이벤트 핸들러가 실행됩니다.

const outer = document.querySelector('.outer');
outer.addEventListener('click', () => {
  console.log('Outer element clicked');
});

위 코드에서는 outer 요소에 클릭 이벤트를 등록하고 있습니다. 따라서 button 요소를 클릭하면, "Outer element clicked"이라는 메시지가 출력됩니다.

버블링 중단하기

이벤트 버블링은 타깃 이벤트에서 시작해서 <html> 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생합니다. 몇몇 이벤트는 window 객체까지 거슬러 올라가기도 합니다. 이 때도 모든 핸들러가 호출됩니다.

그런데 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있습니다.

이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 됩니다.

<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
  <button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>

위 예시에서 <button>을 클릭해도 body.onclick은 동작하지 않습니다.

버블링은 항상 막아야 할까?

꼭 필요한 경우를 제외하곤 버블링을 막지 않는것이 좋습니다. 

예를 들어, 사람들의 행동 패턴을 분석하기 위해 클릭이벤트를 전부 감지하도록 한 경우, stopPropagation으로 버블링을 막아놓은 영역에서는 분석시스템의 코드가 동작하지 않기 때문에 해당부분은 죽은 영역(dead zone)이 되어버립니다.

버블링을 막아야 해결이 되는 문제라면, 핸들러의 event 객체에 데이터를 저장해 다른 핸들러에서 읽을 수 있게 하면, 아래쪽에서 무슨 일이 일어나는지를 부모 요소의 핸들러에게 전달할 수 있으므로, 이 방법으로도 이벤트 버블링을 통제할 수 있습니다.

이벤트 위임

이벤트 위임은 이벤트 버블링을 기반으로 구현하는 방식입니다. 

즉, 하위요소에 각각 이벤트 핸들러를 등록하는 것이 아니라, 하위요소에서 발생한 이벤트를 상위 요소에서 처리하는 방식으로, 하위 요소에 이벤트 핸들러를 등록하지 않고 상위 요소에서 이벤트 핸들러를 등록하여 처리합니다. 이 방식을 사용하면 동적으로 생생된 하위요소에 대해서도 이벤트 핸들러를 등록할 수 있으며, 이벤트 핸들러 등록 수를 줄일 수 있고, DOM에 대한 조작 횟수를 줄여 성능을 향상시킬 수 있습니다. 

등록 수 감소

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
</ul>

여기서 각 li 요소에 클릭 이벤트 핸들러를 등록한다면, 총 4개의 이벤트 핸들러가 등록됩니다. 하지만 ul 요소에 클릭 이벤트 핸들러를 등록하고, 이벤트 핸들러 내부에서 이벤트가 발생한 요소를 판별하여 처리하는 방식을 사용하면, 이벤트 핸들러를 1개만 등록해도 모든 li 요소의 클릭 이벤트를 처리할 수 있습니다.

const listEl = document.getElementById('list');

listEl.addEventListener('click', (event) => {
  if (event.target.nodeName === 'LI') {
    console.log('Clicked:', event.target.innerText);
  }
});

위 코드에서는 ul 요소에 클릭 이벤트 핸들러를 등록하고, 이벤트 핸들러 내부에서 **event.target**을 사용하여 이벤트가 발생한 요소를 판별하고 처리합니다. 이렇게 하면, 이벤트 핸들러를 1개만 등록해도 모든 li 요소의 클릭 이벤트를 처리할 수 있습니다.

 

동적으로 생성된 하위 요소에 이벤트 핸들러 등록

이벤트 위임을 사용하면, 동적으로 생성된 하위 요소에 대해서도 이벤트 핸들러를 등록할 수 있습니다. 예를 들어, JavaScript 코드로 새로운 li 요소를 생성하고, ul 요소에 추가할 때, 이벤트 핸들러를 등록하지 않아도 자동으로 이벤트가 처리됩니다.

const listEl = document.getElementById('list');

listEl.addEventListener('click', (event) => {
  if (event.target.nodeName === 'LI') {
    console.log('Clicked:', event.target.innerText);
  }
});

const newItemEl = document.createElement('li');
newItemEl.innerText = 'Item 5';
listEl.appendChild(newItemEl);

위 코드에서는 li 요소를 생성하고, ul 요소에 추가한 후, 클릭 이벤트가 발생하면 자동으로 이벤트가 처리됩니다. 이렇게 하면, 새로운 요소가 추가될 때마다 이벤트 핸들러를 등록할 필요가 없습니다.

 

React17 에서의 변경점

이전 버전에서는 React 이벤트 시스템은 이벤트 캡처링(Event Capturing)을 사용하여 이벤트를 처리하였으나, React 17에서는 이벤트 버블링을 사용하도록 변경되었습니다.

React 17에서는 이벤트 버블링을 사용함으로써, 일부 브라우저에서 발생하는 문제를 방지할 수 있습니다. 이전 버전에서는 이벤트 캡처링을 사용하여 이벤트를 처리하였기 때문에, 일부 브라우저에서 이벤트가 발생한 요소에서 상위 요소로 이벤트가 전파되지 않는 문제가 발생할 수 있었습니다. 이 문제는 React 17에서 이벤트 버블링을 사용함으로써 해결되었습니다.

하지만, 이벤트 캡처링을 사용하고자 하는 경우에는 capture 프로퍼티를 사용하여 이벤트 캡처링을 활성화할 수 있습니다.

function handleClick(event) {
  console.log('Clicked:', event.target);
}

function App() {
  return (
    <div onClick={handleClick} onClickCapture={handleClick}>
      <button>Click me</button>
    </div>
  );
}

위 코드에서는 div 요소에 capture 프로퍼티를 추가하여 이벤트 캡처링을 활성화하고 있습니다. 이렇게 하면, div 요소에서 발생한 클릭 이벤트가 자식 요소인 button 요소로 전파되지 않고, 부모 요소인 div 요소로부터 시작하여 자식 요소로 전파됩니다.

댓글