useRef의 두 번째 얼굴: 비밀 주머니

672025년 10월 21일3

useRef의 첫 번째 얼굴이 DOM을 향해 있었다면, 그 두 번째 얼굴은 순수한 자바스크립트의 세계를 향해 있었다. 이 두 번째 역할은 훨씬 더 미묘했지만, 숙련된 개발자들에게는 강력한 무기가 되었다.

그 비밀은 useRef의 핵심 동작 원리에 숨어 있었다.
const myRef = useRef(initialValue){ current: initialValue }라는 객체를 반환한다.
리액트는 이 myRef 객체가 컴포넌트의 전체 생애 동안 동일한 객체임을 보장한다.

여기서 가장 중요한 특징이 드러났다.
myRef.current 프로퍼티의 값을 직접 변경해도, 리액트는 절대로 리렌더링을 일으키지 않는다.

useStateset 함수를 호출하면 리렌더링이 예약되지만, ref.current = ... 와 같은 할당문은 리액트의 렌더링 시스템에 아무런 신호도 보내지 않았다.

댄은 이 독특한 성질을 활용하는 예제를 보여주었다.
그는 useEffect 안에서 setInterval을 사용해 타이머를 만들었던 이전 예제를 다시 가져왔다.

function StoppableTimer() {
  const [seconds, setSeconds] = useState(0);
  const timerIdRef = useRef(null); // 타이머 ID를 저장할 ref

  const startTimer = () => {
    // 이미 타이머가 실행 중이면 아무것도 하지 않는다.
    if (timerIdRef.current) return;

    timerIdRef.current = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(timerIdRef.current);
    timerIdRef.current = null;
  };

  return (
    <div>
      <p>Seconds: {seconds}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

이 코드에서 timerIduseState가 아닌 useRef를 통해 관리되었다.
왜일까?

“타이머의 ID 값은 UI 렌더링과 아무런 관련이 없습니다.”
댄이 설명했다.
“이 값은 화면에 표시되지 않죠. 단지 clearInterval을 호출하기 위해 내부적으로 ‘기억’해야만 하는 값입니다. 만약 이 값을 useState로 관리한다면, setInterval이 반환된 ID로 상태가 업데이트될 때마다 불필요한 리렌더링이 발생할 겁니다.”

useRef는 바로 이럴 때를 위한 완벽한 도구였다.
리렌더링을 유발하지 않으면서, 컴포넌트의 여러 렌더링 주기에 걸쳐 어떤 값을 계속해서 유지하고 싶을 때.

useRef는 마치 컴포넌트 인스턴스에 달린 ‘비밀 주머니’와 같았다.
개발자는 이 주머니(ref.current)에 무엇이든 담을 수 있었다.

  • 타이머 ID
  • 웹소켓 연결 객체
  • 이전 propsstate의 값
  • DOM 노드 (첫 번째 얼굴)

그리고 이 주머니에 무언가를 넣거나 꺼내는 행위는, 리액트의 감시망을 벗어나 조용히 이루어졌다.

이로써 개발자들은 두 종류의 데이터 저장소를 갖게 되었다.

  1. useState: 화면에 표시되어야 하고, 그 값이 변하면 리렌더링이 일어나야 하는 데이터를 위한 저장소.
  2. useRef: 렌더링과는 무관하지만, 컴포넌트의 생애 동안 계속 유지되어야 하는 값을 위한 저장소.

useRef의 이 두 번째 얼굴은, 훅 패러다임에서 클래스의 ‘인스턴스 프로퍼티(this.myValue)’가 하던 역할을 대체했다.

이제 훅의 기본 무기고는 거의 모든 시나리오를 감당할 수 있을 만큼 강력해졌다.
핵심 훅들(useState, useEffect, useContext, useRef 등)의 설계가 마무리되자, 리액트 팀의 시선은 다시, 그들이 이 여정을 시작했던 최초의 이유, ‘로직 재사용’으로 돌아갔다.

그들은 이미 ‘커스텀 훅’이라는 위대한 발견을 했지만, 이제는 개발자들에게 어떻게 하면 좋은 커스텀 훅을 만들 수 있는지, 그 구체적인 예시와 패턴을 보여줄 필요가 있었다.
모든 것은 가장 단순한 커스텀 훅에서부터 시작될 터였다.