본문 바로가기

Study/Side Projects

[Zeros] stopPropagation vs. event.target !== event.currentTarget

자식 요소와 부모 요소에 동일한 이벤트의 리스너가 등록되어 있으면, 이벤트 버블링이 발생한다. 예를 들어 카드 컴포넌트(부모)와 삭제하기 버튼(자식)에 모두 클릭 리스너가 등록되어 있고, 삭제 버튼을 클릭하면 다음과 같은 일이 발생한다. 

 

 

카드 컴포넌트

 

  1. 삭제하기 버튼에 등록된 리스너 함수가 실행된다. 
  2. 문제는, 버튼이 카드의 자식 요소이기 때문에 발생한다. => 버튼이 클릭되었다는 것은, 카드가 클릭되었다는 것이기도 하기 때문이다. 
  3. 카드가 클릭되기도 했으므로, 카드에 등록된 리스너 함수가 실행된다. 

 

나의 경우 카드를 클릭했을 때, 해당 카드의 정보를 더 자세히 볼 수 있는 페이지로 이동하도록 만들어두었다. 그랬더니 삭제하기를 클릭했을 때 페이지 이동이 발생해서, 삭제 기능을 사용할 수가 없는 문제가 생겼다. 뿐만 아니라, 저축하기, 수정하기 기능까지 카드 컴포넌트에 포함되어 있기 때문에 해당 기능들이 모두 제대로 작동하지 않았다. 이를 해결하기 위해 다음과 같은 방법들을 생각해 보았다. 

 

  1. 저축, 삭제, 수정 버튼에 id를 등록하고, 카드 컴포넌트 클릭 리스너에서 event.target.id 별로 로직을 작성하기
    • event.target vs event.currentTarget 
      • event.target | 사용자가 이벤트를 발생시킨 요소를 말한다. 삭제 버튼을 클릭해서 이벤트가 발생되었을 때 event.target은 삭제 버튼이 된다.
      • event.currentTarget | 이벤트 리스너가 등록된 요소를 가리킨다. 삭제 버튼을 클릭해서 실행되는 리스너 함수는 카드 컴포넌트 이므로, currentTarget은 카드 컴포넌트가 된다. 
    • 바닐라 js에서와 달리 리액트에서는 event.target.id 값을 읽을 수 없어서 이 방법을 적용하지 못했다. 
  2. 저축, 삭제, 수정 버튼 마다 클릭 event를 각각 등록하고, stopPropagation을 호출해서 이벤트 버블링을 강제로 막기
    • stopPropagation을 남발하면 유지 보수하기 어려운 코드가 된다는 것을 (드림코딩 강의에서 들었다) 배운 적이 있어서, 지양해야 하는 것을 알고 있었다. 그럼에도 다른 방법을 떠올리기 어려워서... 처음에는 이 로직대로 작성했다. 
    • ??? 삭제 버튼 클릭했을 때 진짜 삭제할 건지 물어보는 모달을 띄우고 있었는데, 이 모달의 확인 버튼을 클릭하는 이벤트에서도 stopPropagation을 호출해 주어야 했다. 심지어 모달은 createPortal로 만들어서 카드 컴포넌트의 자식 요소도 아님. 
  3. 카드 컴포넌트에서 if(event.target !== event.currentTarget) return; 로직 추가하기 
    • 여기 저기서 stopPropagation을 사용하고 있는 것이 마음에 안들어서, 다른 방법을 생각 해 보다가 이 방법을 잊고 있었다는 사실이 떠올랐다. 
    • 대신 저축, 삭제, 수정 버튼 뿐만 아니라 저축 목표와 같은 타이틀(heading tag)을 클릭했을 때에도 return 된다.  

 

  const onClickCard: React.MouseEventHandler = (event) => {
    if (event.target !== event.currentTarget) return;

   // 자식 컴포넌트가 아닌 카드 컴포넌트 자체를 클릭했을 때에만 navigate 실행 
    navigate(`${id}`);
  };

 

결론: 카드 컴포넌트는 너무 많은 일을 수행하기에 적합하지 않다.