TIL: 이벤트 프로퍼티 방식과 addEventListener 메서드 방식(feat: 원티드 프리온보딩 지원과제)

Yeongjong Kim·2022년 1월 23일
1

TIL

목록 보기
3/10

react에서 document에 addEventListener로 핸들러 바인딩을 한다면, 어떤 일이 발생할까?

MouseDown 이벤트가 발생하면, document에 mousemove 핸들러가 바인딩 된다. mousemove 이벤트는 적절한 시기에 removeEventListener로 해제되어야 한다. 그렇지 않으면 분명 의도하지 않은 순간에 핸들러가 실행되며 프로그램이 의도치 않게 동작해 버린다.

케로셀로 드는 예시

이하 컴포넌트는 스와이프기능을 가진 케로셀 예시이다. 아래 코드를 보면 mousemove 이벤트에 스와이프 핸들러가 바인딩되었다. 아까 말했듯 이 핸들러를 적절한 순간에 해제하지 않으면 swape 기능이 의도치 않은 순간에 동작해서 슬라이드가 마우스를 따라다닌다. 해제되는 시가는 swapeEnd가 실행되는 순간으로 mouseUp, mouseLeave 이벤트가 실행될 때이다.

function Carousel() {
  return <ul
    onMouseDown={event => {
      if (isMoving.current) return;
      event.preventDefault();
      initialMousePosX = event.clientX - offsetX;
      document.addEventListener('mousemove', swapeStart);
    }}
    onMouseUp={swapeEnd}
    onMouseLeave={swapeEnd}
  >
  ...
  </ul>
}

구현도중 마주친 문제점

mouseUp 이벤트는 ul 태그에 바인딩 되어있기 때문에 그 외부 영역에서는 발생하지 않는다. 이 문제 때문에 Leave 이벤트가 발생하면 바인딩이 해제되도록 구현했으나, 분명 코드상으로 swapeStart 핸들러가 해제되었음에도 불구하고, 해제가 안되고 슬라이드가 마우스를 따라다녔다. 콘솔을 보면 mouseup 이벤트가 발생한 순간에
'end' 문구가 찍힌다. 이는 위 코드상에 드러나지 않지만 removeEventListener('mousemove', swapeStart)이라고 생각하면 된다.

해결 방법

  1. removeEventLister를 useEffect로 적합한 시기에 실행시키면 된다. 그런데 어느 순간에 지워야 하는가?
  2. 리렌더가 발생하는 순간에 지워져야하고,
  3. mouseUp, mouseLeave의 순간에 지워져야하는데 mouseUp은 특히 ul태그를 벗어나서도 동작하도록 구현해야 한다.

나머지는 그렇다 치고, mouseup 이벤트가 중첩 바인딩 되지 않도록 아래 코드가 적절한 시기에 실행되어야 하기에 useEffect로 적절한 시기에 호출해야한다. 상당히 복잡해 보인다. 실제로 해보면 더 그렇다.

document.addEventListener('mouseup', () => { 
  document.removeEventListener('mousemove', swapeStart);
});
  1. 프로퍼티 바인딩 방법을 사용하자.

문제점의 근원은 리렌더가 발생할 때마다 핸들러가 중첩 바인딩 되어 여러번 호출되기 때문이다. 중첩된 핸들러를 개별적으로 각각 해제해야하기 때문에 그 중 한개라도 지워지지 않으면, 의도하지 않은 방향으로 흘러간다. 따라서 프로퍼티 방식을 사용해서 평생 단 하나의 이벤트만 바인딩 되도록 구조를 수정해보자.

function Carousel() {
  return <ul
    onMouseDown={event => {
      if (isMoving.current) return;
      event.preventDefault();
      initialMousePosX = event.clientX - offsetX;
      document.onmousemove = swapeStart; // 수정된 사항은 바인딩 방식 한줄
    }}
    onMouseUp={swapeEnd}
    onMouseLeave={swapeEnd}
  >
  ...
  </ul>
}

분명 핸들러를 해제해야함은 똑같지만, 중첩 바인딩에 대한 우려를 덜 수 있고 이 구조로 인해 코드가 훨씬 깔끔해졌다. 이번 예시 처럼 한 줄로 문제가 바로 해결되는 경우도 있다.

소결론

이벤트 바인딩 방식의 차이점을 이해하고, 적합한 순간에 사용하자! 굿.

profile
Front 💔 End

0개의 댓글