useInterval 사용해서 setInterval 사용 문제 해결

허재원·2022년 12월 27일
0

이전 코드

let currentIndex = 0;

useEffect(() => {
  const slide = setInterval(() => {
  document.getElementsByClassName("masthead-slide")[currentIndex].classList.remove("active");

  currentIndex++;

  if (currentIndex > 4) currentIndex = 0;

  document.getElementsByClassName("masthead-slide")[currentIndex].classList.add("active");
  }, 4500);

  return () => {
    clearInterval(slide);
  };
}, [currentIndex]);

개발 공부를 시작하고 처음 공부하고 자동 슬라이드를 구현하기 위해 다음처럼 작성했다. currentIndex 변수에 대해 다음과 같은 경고를 볼 수 있다.

Assignments to the 'currentIdx' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.

  • React Hook useEffect 내부의 'currentIdx' 변수에 대한 할당은 각 렌더 후에 손실됩니다. 시간이 지남에 따라 값을 보존하려면 useRef Hook에 저장하고 변경 가능한 값을 '.current' 속성에 유지합니다. 그렇지 않으면 이 변수를 useEffect 내부로 직접 이동할 수 있습니다.

자동 슬라이드 기능을 구현하면서 setInterval()를 사용하는데에 있어 문제점이 있다는 것을 인식하고 이를 useInterval 커스텀 훅을 사용해서 개선할 예정이다.

useInterval

// hooks/useInterval.ts
import { useEffect, useRef } from "react";

type UseInterval = {
  (callback: () => void, interval: number): void;
}

const useInterval: UseInterval = (callback, interval) => {
  const savedCallback = useRef<(() => void) | null>(null);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    const tick = () => {
      if (savedCallback.current) {
        savedCallback.current();
      }
    }

    const id = setInterval(tick, interval);

    return () => clearInterval(id);
  }, [interval])
}

export default useInterval;

useRef 활용

const savedCallback = useRef<(() => void) | null>(null);
  • useRef 훅을 사용하여 리렌더링을 방지한다.
  • useRef는 함수형 프로그래밍에서 사용하는 ref로 초기화된 ref 객체인 { current: null }을 반환하며 반환된 객체는 컴포넌트의 전 생애주기 동안 유지되어 useRef로 관리하는 값은 값이 변경되어도 컴포넌트가 리렌더링되지 않는다.

callback 함수를 저장하는 useEffect

useEffect(() => {
  savedCallback.current = callback;
}, [callback]);
  • callback 데이터가 바뀔 때마다 useEffect가 실행되어 savedCallback의 current 값이 새로운 callback 데이터로 업데이트된다고 보면 된다.
  • useInterval 훅은 리렌더링될 때마다 실행되어 업데이트된 state값을 가질 수 있게 만들고, state값을 업데이트하는 함수를 useInterval 훅에 전달하여 내부에서 savedCallback.current에 저장하여 새로 업데이트된 state 값을 callback 데이터로 업데이트된다.

React 라이프사이클에 맞춘 setInterval

useEffect(() => {
  const tick = () => {
    if (savedCallback.current) {
      savedCallback.current();
    }
  }

  const id = setInterval(tick, interval);

  return () => clearInterval(id);
}, [interval])
  • 위의 처리 과정을 후 드디어 saveCallback.current의 함수를 실행시킨다. 이 useEffet를 통해 setInterval하고 unmount될때 clearInterval을 해준다.
  • useEffect가 무한히 실행되는 것을 interval가 변경될 때에만 타이머를 재실행하게 되며 처음 useEffect는 콜백함수가 변경될때마다 업데이트하기 때문에 위 useEffect의 setInerval 함수는 재실행도지 않고 새로 업데이트된 콜백함수를 실행할 수 있다.

useInterval 적용

useInterval(() => {
  slideRef.current.forEach((item) => item.classList.remove('active'));
  slideRef.current[currentIdx].classList.add('active');
  setCurrentIdx((prev) => (prev >= 4 ? 0 : prev + 1));
}, 4500);
  • useInterval을 사용해서 React 라이프 사이클에 맞춘 setInterval을 사용하고, state를 사용해도 문제 없이 자동 슬라이드가 구현이 되었다.

실행 결과

0개의 댓글