본문 바로가기

웹/React

setTimeout을 취소할 때 useRef를 쓰는 이유

반응형

일정 시간이 지나고 나서야 어떤 행동을 수행하도록 할 때 setTimeout을 쓰게 됩니다.

다만 모종의 이유로 그 행동이 취소됐을 때에는 그 id를 가지고 설정된 timeout을 해제해야 하는데,

그 id를 저장하는데 에 useRef가 쓰입니다.

 

const DelayedHoverMenu: React.FC = () => {
  const [showMenu, setShowMenu] = useState(false);
  const hoverTimer = useRef<number | null>(null);

  const handleMouseEnter = () => {
    hoverTimer.current = window.setTimeout(() => {
      setShowMenu(true);
    }, 300);
  };

  const handleMouseLeave = () => {
    if (hoverTimer.current) {
      clearTimeout(hoverTimer.current);
      hoverTimer.current = null;
    }
    setShowMenu(false);
  };

  useEffect(() => {
    return () => {
      if (hoverTimer.current) {
        clearTimeout(hoverTimer.current);
      }
    };
  }, []);

  return (
    <div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
      Hover me!
      {showMenu && <div>Menu content...</div>}
    </div>
  );
};

export default DelayedHoverMenu;

위의 컴포넌트는 마우스가 요소 위에 0.3초 동안 머물렀을 때 메뉴를 나타내고, 마우스가 요소를 벗어났을 때 메뉴를 숨깁니다.

useRef는 타이머 ID를 저장하는데 사용되며, useEffect는 컴포넌트가 언마운트될 때 타이머를 정리하기 위해 사용됩니다.

 

`useRef`를 사용하는 이유는 컴포넌트에서 변경 가능한 참조 값을 가지기 위해서입니다. 이 경우에는 타이머의 ID를 저장하고 관리하기 위해 사용됩니다.

`setTimeout` 함수는 호출 후 일정 시간이 지난 다음 주어진 함수를 실행하는데, 이 때 반환되는 값은 타이머의 ID입니다. 이 ID를 사용하여 `clearTimeout` 함수를 호출하면 타이머를 취소할 수 있습니다.

이 문제의 경우에, 사용자가 요소 위에 마우스를 올리고 0.3초 내에 마우스를 올려놓은 상태에서 벗어나면 메뉴를 보여주지 않아야 합니다. 따라서 `setTimeout`으로 설정한 타이머를 취소해야 합니다. 이를 위해 `setTimeout`의 반환값인 타이머 ID를 `useRef`에 저장하고, 필요한 경우 이를 참조하여 `clearTimeout`을 호출합니다.


const handleMouseEnter = () => {
  hoverTimer.current = window.setTimeout(() => {
    setShowMenu(true);
  }, 300);
};

const handleMouseLeave = () => {
  if (hoverTimer.current) {
    clearTimeout(hoverTimer.current);
    hoverTimer.current = null;
  }
  setShowMenu(false);
};
위 코드에서 `handleMouseEnter`에서는 `setTimeout`을 사용하여 0.3초 후에 메뉴를 보여주는 작업을 예약하고, 그 때의 타이머 ID를 `hoverTimer.current`에 저장합니다.

`handleMouseLeave`에서는 사용자가 요소를 벗어났을 때 `hoverTimer.current`에 저장된 타이머 ID를 참조하여 `clearTimeout`을 호출하고, 메뉴를 숨깁니다.

이런 식으로 `useRef`는 React 컴포넌트 내에서 변경 가능한 값을 저장하고 참조하기 위해 사용됩니다.
 

왜 useRef에 저장해?

`useRef`를 사용하는 주된 이유는 React에서 컴포넌트의 리렌더링에 영향을 주지 않으면서도 값을 참조하거나 변경할 수 있기 때문입니다. 

만약 `useState`를 사용해서 타이머 ID를 저장하게 된다면, 타이머 ID의 값이 변경될 때마다 컴포넌트가 리렌더링 될 것입니다. 그러나 실제로 우리는 그 값의 변경으로 인한 컴포넌트의 리렌더링을 원하지 않습니다. 이런 경우에 `useRef`가 유용하게 쓰입니다.

`useRef`의 특징:
1. `useRef`는 `.current` 프로퍼티를 통해 접근될 수 있는 변경 가능한 ref 객체를 반환합니다.
2. `useRef`의 값이 바뀌어도 컴포넌트는 리렌더링되지 않습니다.
3. `useRef`는 컴포넌트의 여러 렌더링 사이에서도 값을 유지합니다.

이런 특성 때문에, 값의 변경에 따라 리렌더링을 피하면서도 그 값을 컴포넌트 내에서 지속적으로 유지하고자 할 때 `useRef`를 사용합니다. 타이머 ID, DOM 요소, 외부 라이브러리의 인스턴스 등, 리액트의 렌더링 사이클에 영향을 주지 않는 값을 관리할 때 주로 사용됩니다.

 

그러면 전역변수로 선언하는거랑 무슨 차이가 있어?

React 컴포넌트 내에서 useRef를 사용하는 것과 전역 변수를 사용하는 것은 다음과 같은 주요 차이점이 있습니다:

 

 

생명주기

useRef: 컴포넌트의 인스턴스 별로 고유한 ref 객체를 가집니다. 따라서 같은 컴포넌트가 여러 번 인스턴스화되어도 각 인스턴스는 자신만의 useRef 값을 갖게 됩니다. 컴포넌트가 언마운트될 때, 그와 관련된 ref 값도 사라지게 됩니다.
 
전역 변수: 전역 스코프에 선언되므로, 애플리케이션의 전체 수명주기 동안 값이 유지됩니다. 여러 컴포넌트나 함수에서 공유될 수 있습니다.
 

캡슐화

useRef: 컴포넌트 내부에서만 접근 가능하므로, 다른 컴포넌트나 외부 코드에 의해 실수로 변경될 위험이 적습니다.
 
전역 변수: 다른 코드 부분에서도 접근 가능하므로, 실수로 변경되거나 오용될 위험이 높습니다.
 

리렌더링

useRef: 값의 변경이 컴포넌트의 리렌더링을 발생시키지 않습니다.
 
전역 변수: 단순한 전역 변수의 값 변경은 또한 리렌더링을 발생시키지 않습니다. 그러나 React의 상태 관리와 관련된 전역 상태 관리 도구 (예: Redux)를 사용할 경우 값의 변경이 리렌더링을 발생시킬 수 있습니다.
 

 

코드의 가독성 및 유지 보수성

useRef: 해당 변수의 사용 범위와 목적이 컴포넌트 내부로 한정되므로, 코드의 가독성과 유지 보수성이 더 좋을 수 있습니다.
 
전역 변수: 너무 많은 전역 변수는 코드의 가독성과 유지 보수성을 저하시킬 수 있습니다.
결론적으로, 전역 변수와 useRef는 사용 케이스에 따라 선택해야 합니다. 컴포넌트 내에서만 사용되는 변수나 참조 값이 필요할 때는 useRef를 사용하고, 여러 컴포넌트나 함수에서 공유되어야 하는 값이 필요할 때는 전역 변수나 상태 관리 도구를 사용하는 것이 좋습니다.
반응형

' > React' 카테고리의 다른 글

Context  (0) 2022.07.13