이벤트 버블링과 캡처링에 대해 설명하고 경험해보기

ChoiYongHyeun·3일 전
0

브라우저

목록 보기
16/16
post-thumbnail


이벤트 전파 단계

출처 : Event bubbling, 이벤트 버블링이란? :: 박상준의 기술블로그 [1]

액츄얼 돔 노드에 장착된 이벤트 핸들러는 최상단 window 노드로부터 해당 노드까지 이벤트가 전파되는 3가지 단계를 거친다.

  • event capture phase : 이벤트가 발생하여 최상위 노드로부터 이벤트가 발생한 노드까지 이벤트가 전파 되는 과정
  • event target phase : 이벤트가 발생한 노드에게 도달한 단계
  • event bubblng phase : 이벤트가 최상위 노드까지 전파되는 과정

이벤트 핸들러가 이벤트를 캐치하는 순간은 바로 버블링 단계에서 일어난다.[2]

<body>
  <div id="container">
    <button>여기를 클릭하세요!</button>
  </div>
  <pre id="output"></pre>
</body>
<script>
	const output = document.querySelector("#output");
	function handleClick(e) {
  output.textContent += `${e.currentTarget.tagName} 요소를 클릭했습니다.\n`;
}

	const container = document.querySelector("#container");
	const button = document.querySelector("button");

	document.body.addEventListener("click", handleClick);
	container.addEventListener("click", handleClick);
	button.addEventListener("click", handleClick);
</script>

이벤트가 발생한 노드까지 도달하기 위한 캡쳐페이즈가 존재하는 것은 상식적으로 이해가 간다.

DOM 자체가 트리 구조의 객체이고 트리 구조의 객체에서 이벤트가 발생한 노드까지 탐색하여 이벤트를 전달해야 하니까 말이다.

그렇다면 의문이 든다.

그럼 이벤트 버블링 단계는 왜 필요할까 ?

이벤트 버블링이 필요한 이유 : 이벤트 위임

사실 텍스트로만 보았을 땐 크게 와닿지 않았는데 이번에 작업하면서 좀 체감했다.

 		<ReferenceItem
            key={url}
            onClick={() => {
              handleClickUrl(url);
            }}
          >
            <ReferenceItem.Align>
              <ReferenceItem.Favicon faviconUrl={faviconUrl} />
              <ReferenceItem.Title>{title}</ReferenceItem.Title>
              <span className="text-[0.8rem] text-gray-400 flex gap-1">
                <span
                  className={`text-primary
              ${isUsed ? "" : "hidden"}`}
                ></span>
                [{id}]
              </span>
              <ReferenceItem.EraseButton id={id} title={title} />
              <ReferenceItem.RemoveButton title={title} />
            </ReferenceItem.Align>
            {url === clickedUrl && (
	...
          </ReferenceItem>

다음과 같은 컴포넌트에서 ReferenceItem 에 존재하는 온클릭 이벤트 핸들러는

클릭 이벤트 발생시 특정 상태를 변경 시켜 밑에 다양한 버튼을 노출 시키는 컴포넌트이다.

이 때 생각해보면 실제 이벤트가 발생한 event.targetReferenceItem (li) 태그가 아닌 내부에 존재하는 다른 자식 태그 들이다.

만약 버블링 단계가 없었다면 ReferenceItem 내부에 존재하는 모든 태그들에 모두 온클릭 이벤트를 달아줬어야 했을 것이다.

하지만 버블링 단계에서 부모 태그가 자식 태그에서 발생한 이벤트를 캡쳐 할 수 있게 되어 자식 태그에서 핸들링 해야 할 이벤트를

부모태그 한 곳에서만 핸들링 하여 이벤트 처리를 위임 해줄 수 있게 되었다.

이벤트 전파를 의도적으로 막아야 하는 경우

이벤트의 전파를 막기 위해선 이벤트 핸들러에서 이벤트 객체에게 stopPropagation() 메소드를 호출해줘야 한다.

이벤트 전파 (propagation)가 막힌 노드는 이벤트의 전달 방향이 캡쳐링 단계건 버블링 단계건 상관 없이 더 이상 이벤트를 전파 하지 않는다.

그럼 이런 경우가 언제 필요 할까?

	<ReferenceItem
            key={url}
            onClick={() => {
              handleClickUrl(url);
            }}
          >
       	...
              <ReferenceItem.EraseButton id={id} title={title}/>
              <ReferenceItem.RemoveButton title={title} />

	...
          </ReferenceItem>

주로 동일한 이벤트 핸들러가 부모와 자식이 모두 갖는 경우에 해당한다.

아이템에 붙어 있는 클릭 이벤트 핸들러 뿐 아니라 자식에 존재하는 ...Button 컴포넌트도 클릭 이벤트 핸들러를 가지고 있다.

export const Button = ({
  children,
  className = "",
  size = "md",
  onClick,
  ...props
}: ButtonProps) => {
  return (
    <button
      {...props}
      ...
      onClick={onClick}
    >
      {children}
    </button>
  );
};

이벤트 전파를 막지 않은 채로 해당 버튼이 클릭 되면 어떤 일이 일어나는지 보자

나는 자식 노드의 이벤트 핸들러만 발생하길 기대하였는데 (해당 아이템이 상 하로 이동하는 행위)

버튼에서 캡쳐 된 이벤트가 부모 이벤트로 버블링 되면서 부모 이벤트 또한 발생하는 모습을 볼 수 있다.

이러한 문제를 방지하기 위해 이벤트 핸들러에서 전파 과정을 막도록 다음처럼 작성해줄 수 있다.

export const Button = ({
  children,
  className = "",
  size = "md",
  onClick,
  ...props
}: ButtonProps) => {
  return (
    <button
      {...props}
		...
      onClick={(e) => {
        e.stopPropagation();
        if (onClick) {
          onClick(e);
        }
      }}
    >
      {children}
    </button>
  );
};

출처

  1. Event bubbling, 이벤트 버블링이란? :: 박상준의 기술블로그
  2. 이벤트 버블링 - Web 개발 학습하기 | MDN
profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글