티스토리 뷰

javascript

Event - Capturing/Bubbling

kmj24 2021. 9. 1. 19:02

이벤트, Event

Event 

프로그래밍하고 있는 시스템에서 일어나는 사건(action) 혹은(occurrence)이며, 이는 원한다면 그것들에 어떠한 방식으로 응답 할 수 있도록 시스템이 말해주는 것입니다. - mdn -

시스템은 event가 발생될 때 몇몇 종류의 신호를 생산 또는 발생시키도 event가 발생되었을 때 action이 자동적으로 취해질 수 있는 메커니즘을 제공한다.

Web에서, event는 browser 내에서 발생되고, 그것이 거주하는 특정한 요소에 부착되는 경향이 있다.

이것은 하나의 element 또는 element의 집합, 현재 탭에 load된 HTML문서, 전체 브라우저 윈도우 등이 될 수 있다.

이러한 이벤트는 다양한 타입이 있다.

  • 키보드 또는 마우스 handling
  • browser크기 조절
  • form 제출
  • 영상의 start, pause, end
  • 오류 발생

위의 예시 말고도 수많은 이벤트가 존재한다.

https://developer.mozilla.org/en-US/docs/Web/Events

 

Event reference | MDN

This topic provides an index to the main sorts of events you might be interested in (animation, clipboard, workers etc.) along with the main classes that implement those sorts of events. At the end is a flat list of all documented events.

developer.mozilla.org

 

Inline event handler

HTML element의 attribute에 다양한 이벤트들이 존재하며(onclikc, onchange, onkeypress 등) 아래와 같은 패턴으로 사용할 수 있다.

function onclikc() {
  console.log('click event!');
}

<div onclick="onclick()"></div>

Web에서 가장 빠르기 이벤트를 등록하는 방법은 위와 같이 인라인 이벤트 핸들러를 사용할 수 있으며, 이벤트를 등록할 수 있는 다양한 방법들이 있다. (querySelector를 이용한 등록, addEventListener를 이용한 등록)

mdn에서는 인라인 이벤트 핸들러를 사용하지 않을것을 권장한다.
유지보수가 힘들고, HTML과 javascript가 섞여 작성될 경우 분석이 어렵기 때문이라고 한다.

 

이벤트 버블링, Event bubbling

Bubbling은 한 요소에 이벤트가 발생한다면 이 요소에 할당된 handler가 동작하고, 이어 부모 요소의 handler가 동작한다. 최상단의 요소를 만날때 까지 이 과정이 반복되며, 요소 각각에 할당된 handler가 동작한다.

(하위 요소 -> 상위 요소 이벤트 동작)

<form onclick='alert('form')'>
  <div onclick='alert('div')'>
    <span onclick='alert('p')'>
      event bubbling
    </span>
  </div>
</form>

위 코드에서 p태그를 클릭한다면 alert창에 span -> div -> form 차례대로 출력이 된다.

이는 event bubbling에 의한 현상이고 가장 하위 요소인 span태그 부터 document 객체를 만날때 까지 onclick handler가 각각 동작한다.

거의 모든 event는 bubbling되지만, focus 이벤트와 같이 bubbling되지 않는 event도 있다.

 

event.target

부모 요소의 handler는 event.target 메서드를 통해 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있다.

event가 발생한 가장 안쪽의 요소를 target요소라고 부르고, event.target을 사용하여 접근할 수 있다.

event.target과 event.currentTarget은 차이가 있다.

  • event.target은 이벤트가 시작된 요소이고, 버블링이 진행되어도 변하지 않는다.
  • event.currentTarget(this)은 현재 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조한다.

react 코드

import React, { useEffect, useRef } from "react";

function EventComponent() {
  const formRef = useRef<HTMLFormElement>(null);

  useEffect(() => {
    if (formRef.current) {
      formRef.current.onclick = (e: MouseEvent) => {
        console.log(e.currentTarget);
        console.log(e.target);
      };
    }
  }, [formRef]);

  return (
    <form ref={formRef}>
      form
      <div>
        div<p>p</p>
      </div>
    </form>
  );
}

위 코드는 react 컴포넌트이며, form - div - p 태그 형태를 반환한다.

form태그 영역 내에 마우스 click 이벤트가 발생하면 console.log를 출력한다.

각각 요소를 클릭하면 다른 결과가 나온다.

p태그 클릭

p태그를 누른다면 currentTarget은 form영역 전체가 출력되고, target은 p영역만 출력된다.

div태그 클릭

div태그를 누른다면 currentTarget은 form영역 전체가 출력되고 target은 div영역이 출력된다.

form태그 클릭

form태그를 누른다면 currentTarget은 form영역 전체가 출력되고 target도 form영역이 출력된다.

위에서 test한 결과로 currentTarget은 event bubbling이 진행되어, form태그영역을 출력한다.

 

event bubbling 중단

event bubbling은 target event부터 document객체를 만날때 까지(몇몇 event는 window까지), 각 노드에서 모든 event를 발생시킨다. 이렇게 자동으로 진행되는 event bubbling을 event.stopPropagation 메서드를 이용하여 중단하도록 명령할 수 있다.

아래의 코드를 보자

function EventComponent() {
  const divRef = useRef<HTMLDivElement>(null);

  return (
    <form
      onClick={() => {
        alert("form event");
      }}
    >
      form
      <div ref={divRef}>
        div
        <p
          onClick={() => {
            alert("p event");
          }}
        >
          p
        </p>
      </div>
    </form>
  );
}

export default EventComponent;

p 태그를 클릭하면 'p event', 'form event' alert창이 차례대로 출력된다.

하지만 p태그를 클릭했을때 'form event' alert창은 출력되지 않기를 원한다.

이를 위해 div 태그에서 event bubbling을 중단하는 로직을 추가할 수 있다.

아래의 코드는 div에서 e.stopPropagation() 메서드를 이용하여 bubbling을 중단한다.

아래의 코드를 이용하여 div태그에서 부모 요소로의 bubbling을 멈추게 할 수 있다.

import React, { useEffect, useRef } from "react";

function EventComponent() {
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (divRef.current) {
      divRef.current.onclick = (e: MouseEvent) => {
        e.stopPropagation();
      };
    }
  }, [divRef]);

  return (
    <form
      onClick={() => {
        alert("form event");
      }}
    >
      form
      <div ref={divRef}>
        div
        <p
          onClick={() => {
            alert("p태그");
          }}
        >
          p
        </p>
      </div>
    </form>
  );
}

export default EventComponent;

 

 

event.stopPropagation()메서드는 위쪽 요소로의 bubbling은 막아주지만, 아래쪽 요소로부터 올라온 event는 여전히 동작한다. 아래에서 올라오는 event를 막기 위해 event.stopImmediatePropagation()메서드를 이용할 수 있다. bubbling도 멈추고, handler의 동작도 발생하지 않는다.

divRef.current.onclick = (e: MouseEvent) => {
  e.stopImmediatePropagation();
};

 

 

bubbling은 꼭 필요한 경우를 제외하면 막지 않도록 한다.

bubbling을 멈춘 영역은 분석 시스템의 코드가 동작하지 않으므로 dead zone이 되어 버린다.

반드시 버블링을 막아야 되는 상황이라면 커스텀 이벤트 등을 사용하여 문제를 해결할 수 있다.

 

이벤트 캡처링, Event Capturing

event capturing은 event bubbling과는 반대로 event가 상위 요소에서 하위 요소로 전달되는 단계이다.

표준 DOM event에서 정의한 event flow에는 3단계가 있다.

  1. capturing 단계 : event가 상위 요소에서 하위 요소로 전파
  2. target 단계 : event가 실제 target 요소에 전달되는 단계
  3. bubbling 단계 : event가 상위 요소로 전파되는 단계

capturing을 잡아내려면 addEventListener에서 3번째 인자로 true를 넘겨주어야 한다.

ref.addEventListener(..., true);

이는 capture옵션이고, default값은 false이고 bubbling 단계에서 동작하며, true라면 capturing단계에서 진행된다.

import React, { useEffect, useRef } from "react";

function EventComponent() {
  const formRef = useRef<HTMLFormElement>(null);
  const divRef = useRef<HTMLDivElement>(null);
  const pRef = useRef<HTMLParagraphElement>(null);

  const eventBubbling = (e: MouseEvent) => {
    console.log(`bubbling`);
    console.log(e.currentTarget);
  };

  const eventCapturing = (e: MouseEvent) => {
    console.log(`capturing`);
    console.log(e.currentTarget);
  };

  useEffect(() => {
    formRef.current?.addEventListener("click", eventCapturing, true);
    formRef.current?.addEventListener("click", eventBubbling);
    return () => {
      formRef.current?.removeEventListener("click", eventCapturing, true);
      formRef.current?.removeEventListener("click", eventBubbling);
    };
  }, [formRef]);

  useEffect(() => {
    divRef.current?.addEventListener("click", eventCapturing, true);
    divRef.current?.addEventListener("click", eventBubbling);
    return () => {
      divRef.current?.removeEventListener("click", eventCapturing, true);
      divRef.current?.removeEventListener("click", eventBubbling);
    };
  }, [divRef]);

  useEffect(() => {
    pRef.current?.addEventListener("click", eventCapturing, true);
    pRef.current?.addEventListener("click", eventBubbling);
    return () => {
      pRef.current?.removeEventListener("click", eventCapturing, true);
      pRef.current?.removeEventListener("click", eventBubbling);
    };
  }, [pRef]);

  return (
    <form ref={formRef}>
      form
      <div ref={divRef}>
        div
        <p ref={pRef}>p</p>
      </div>
    </form>
  );
}

form태그, div태그, p태그에 모두 capturing, bubbling을 구분하기 위해 eventCapturing, eventBubbling 함수를 등록해두었다.

capturing을 먼저 등록했으므로 capturing이 먼저 실행된다.

p태그를 눌렀을 경우 capturing은 상위 요소로 부터 하위 요소로, bubbling은 하위 요소부터 상위 요소로 출력되는것을 확인할 수 있다.

  1. capturing => form → div → p
  2. bubbling => p → div → form

임을 확인할 수 있었다.

 

event.target => Event가 발생한 영역에서 가장 안쪽의 영역 

event.currentTarget(this) => Event가 발생한 영역

event.eventPhase => 현재 event flow level을 출력

  • capturing = 1
  • target = 2
  • bubbling = 3

 

위의 코드에서 p태그 영역을 클릭했을 때, eventPhase도 출력해보면 capturing을 하는 form, div는 1로 출력

target인 p는 2로 출력된다.(bubbling과 capturing 모두 발생하였으므로 p태그 영역은 2번 호출된다.)

bubbling의 경우에는 3으로 출력됨을 알수 있다.

 

참고

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Building_blocks/Events

 

이벤트 입문 - Web 개발 학습하기 | MDN

이벤트(event)란 여러분이 프로그래밍하고 있는 시스템에서 일어나는 사건(action) 혹은 발생(occurrence)인데, 이는 여러분이 원한다면 그것들에 어떠한 방식으로 응답할 수 있도록 시스템이 말해주

developer.mozilla.org

https://ko.javascript.info/bubbling-and-capturing

 

버블링과 캡처링

 

ko.javascript.info

'javascript' 카테고리의 다른 글

Javascript currying  (0) 2021.06.26
Javascript와 비동기(asyncronous), Callback/Promise/async await  (0) 2021.06.25
this, arrow function  (0) 2021.06.25
Event Loop  (0) 2021.05.11
closer function  (0) 2021.04.28
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함