setTimeout을 이벤트로 리펙토링하기

camel·2023년 10월 21일
0

Professional Insights

목록 보기
4/4
post-thumbnail

✅ 개요

회사 프로젝트를 진행하면서 문제가 생겼다.
문제는 Animation이 끝나기도 전에 상태값이 변하여 애니메이션을 캔슬하는 것이었다.

그래서 스로틀링으로 이벤트를 막아주려고 하였다.

스로틀링(Throttling)
일정한 시간 간격(예: 100ms)으로 스크롤 이벤트를 처리합니다. 스로틀링은 일정 시간 동안 이벤트가 발생하면 처음 한 번만 이벤트를 실행하고, 그 후 일정 시간이 지난 후에 다시 이벤트를 실행합니다. 이를 통해 스크롤 이벤트가 빈번하게 발생할 때 이벤트 핸들러의 실행을 제어할 수 있습니다.


✅ 코드 적용

const isAnimaitionRef = useRef(false);

function handleOnFocusedItem(event) {
  if (isAnimaitionRef.current) return; // 이미 애니메이션 중이면 무시
    isAnimaitionRef.current = true; // 애니메이션 시작
  
    // 일정 시간(예: 100ms) 후에 false로 변경하여 다음 이벤트가 실행되도록 함
    setTimeout(() => {
      !isAnimaitionRef.current = false;// 이벤트 끝
    }, 100);
  
}

위처럼 애니메이션이 작동하는 시간동안 이벤트 캔슬이 이루어지도록 하였다.

하지만 예상처럼 작동하진 않았다.
setTimeout이 10번 중에 3번정도 올바르게 이루어지지 않았다.

문제 원인은
기존 코드에 setTimeout이 너무 많아서 이벤트 루프가 올바르게 작동하지 못한다고 생각했고 이로 인해 동기화 이슈를 의심하였다.
정확한 동기화가 이루어질려면 애니메이션 변화를 감지하는 방법이 필요했다.

브라우저 내 움직임이라면 분명 이벤트가 있을 것이라고 생각했고,
transitionend를 찾을 수 있었다.

✅ setTimeout -> transitionend

 const isAnimationRef = useRef(false); // 애니메이션 중인지 아닌지를 추적
  const transitionElementRef = useRef(null); // 대상 요소의 참조를 저장

  // 컴포넌트가 마운트될 때 한 번만 실행
  useEffect(() => {
    transitionElementRef.current = document.querySelector('.focusBox'); // 대상 요소 선택

    const handleTransitionEnd = () => {
      if (isAnimationRef.current) { // 애니메이션 중이라면
        isAnimationRef.current = false; // 애니메이션 상태를 false로 설정
      }
    };

    // 대상 요소가 존재하면
    if (transitionElementRef.current) {
      transitionElementRef.current.addEventListener('transitionend', handleTransitionEnd);
    }

    // 컴포넌트가 언마운트될 때 이벤트 리스너 제거
    return () => {
      if (transitionElementRef.current) {
        transitionElementRef.current.removeEventListener('transitionend', handleTransitionEnd);
      }
    };
  }, []);

  const handleOnFocusedItem = (event) => {
    if (isAnimationRef.current || !transitionElementRef.current) return; 
    // 애니메이션 중이거나 대상 요소가 없으면 무시

    isAnimationRef.current = true; // 애니메이션 시작
    // 이제 transitionend 이벤트가 발생하면 handleTransitionEnd가 실행
  };

기존 setTimeout은 로직 안에서 이루어졌지만 이벤트를 사용할 경우 리스너를 추가해줘야한다.
함수 안에서 이루어질 경우 빈번하게 추가와 제거가 발생하므로 마운트 초기에 넣을 수 있도록 useEffect 안에 작성하였다.

transition width가 감소할 때를 체크하기 위해 만든 로직인데,
증가하는 경우도 고려하여서

      if (isAnimationRef.current) { // 애니메이션 중이라면
        isAnimationRef.current = false; // 애니메이션 상태를 false로 설정
      }

위 로직을 추가했다. 왜냐면 늘어날때는 위 로직이 작동하지 않는게 좋았다.

로직이 많이 길어졌지만, 동기화가 제대로 이루어졌고
부드럽게 잘작동하여 전보다 UX가 좋아졌다.


🙌 배운 점

이번 개선을 통해 많은 이벤트들이 있는 것을 알게되었고 이를 효과적으로 사용하지 못하고 있다는 것을 깨달았다.
그래서 움직임이나 동기화 관련된 이슈가 있다면 브라우저 이벤트에 관해 검토하겠다.

profile
화이팅~ 가보자구

0개의 댓글