React Hook - useRef

김강민·2025년 7월 28일

개발

목록 보기
20/32
post-thumbnail

😎 React useRef 완벽 정복: DOM 참조를 넘어서

React로 개발하다 보면 useRef 훅을 마주하게 된다. 대부분의 경우, useRefdocument.getElementById처럼 특정 DOM 요소에 직접 접근하기 위한 도구로 처음 배우게 된다. 하지만 이는 useRef가 가진 능력의 절반에 불과하다.

React 공식 문서와 youtube 영상에서 설명하는 useRef에 대한 내용을 종합해보면, useRef의 진짜 본질은 다른 곳에 있다. 이번 글에서는 내가 이해한 useRef의 핵심 철학과, 이를 통해 어떻게 더 똑똑하고 효율적인 코드를 작성할 수 있는지 정리해보고자 한다.

useRef의 두 가지 얼굴

useRef의 사용법은 명확하게 두 가지로 나눌 수 있다.

  1. DOM 참조 (DOM Referencing): 가장 흔한 사용법이다. ref를 JSX 요소에 직접 연결하여, 해당 DOM 노드에 접근하고 focus().play() 같은 명령형 API를 호출할 때 사용한다.
  2. 값 참조 (Value Referencing): 이 글의 핵심 주제이다. useRef는 DOM 요소뿐만 아니라, 어떤 값이든 담을 수 있는 일반적인 '상자'로 사용될 수 있다.

React 공식 문서는 이 두 번째 역할을 이렇게 정의한다.

"useRef is a React Hook that lets you reference a value that’s not needed for rendering."
(useRef는 렌더링에 필요하지 않은 값을 참조할 수 있게 해주는 React 훅입니다.)

핵심은 "렌더링에 필요하지 않은 값"이다. useRef로 만든 상자 안의 내용물(.current 프로퍼티)을 아무리 바꿔도, 컴포넌트는 절대 리렌더링되지 않는다.

일반 변수 vs useState vs useRef

useRef의 진정한 가치를 이해하려면, 다른 두 가지 값 저장 방식과의 차이점을 알아야 한다.

구분일반 변수 (let)useStateuseRef
리렌더링 시 값 유지X (초기화됨)O (유지됨)O (유지됨)
값 변경 시 리렌더링XO (리렌더링 유발)X
  • 일반 변수 (let renderCount = 0;): 컴포넌트가 리렌더링될 때마다 함수가 새로 호출되므로, 이 변수는 매번 0으로 초기화된다. 이전 값을 기억할 수 없다.
  • useState: 값을 변경하면(setCount) 컴포넌트가 리렌더링된다. 화면에 표시되어야 하는 상태에 적합하다.
  • useRef: 값은 리렌더링되어도 유지되지만, 값을 변경해도 리렌더링되지 않는다. "기억은 해야 하지만, 화면을 바꿀 필요는 없을 때" 사용하는 완벽한 도구이다.

⚠️ 주의: 렌더링 중에는 ref.current를 건드리지 마세요!

useRef를 사용할 때 반드시 지켜야 할 중요한 규칙이 있다. React는 컴포넌트가 순수 함수(Pure Function)처럼 동작하기를 기대한다. 즉, 동일한 입력(props, state)에 대해 항상 동일한 JSX를 반환해야 한다는 것이다.

하지만 렌더링 과정에서 ref.current 값을 읽거나 쓰게 되면, 이 '순수성'이 깨지게 된다.

function MyComponent() {
  // ...
  // 🚩 렌더링 중에 ref 값을 변경하는 것은 안됩니다.
  myRef.current = 123;
  // ...
  // 🚩 렌더링 중에 ref 값을 읽는 것도 안됩니다.
  return <h1>{myOtherRef.current}</h1>;
}

이런 코드는 당장은 동작하는 것처럼 보일 수 있지만, React의 향후 최적화 기능(예: Concurrent Rendering)과 충돌하여 예측할 수 없는 버그를 유발할 수 있다.

ref의 값은 이벤트 핸들러useEffect 안에서만 읽고 쓰는 것이 올바른 방법이다.

function MyComponent() {
  // ...
  useEffect(() => {
    // ✅ Effect 안에서는 ref를 읽거나 쓸 수 있습니다.
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ 이벤트 핸들러 안에서도 ref를 읽거나 쓸 수 있습니다.
    doSomething(myOtherRef.current);
  }
  // ...
}

만약 렌더링 중에 어떤 값을 꼭 사용해야 한다면, 그건 useRef가 아니라 useState를 사용해야 한다는 강력한 신호이다.

🖥️ useRef의 실용적인 예제

useRef를 '값 참조' 목적으로 사용하는 강력한 예제 두 가지를 살펴보자.

1. 렌더링 횟수 카운터

컴포넌트가 몇 번 렌더링되었는지 디버깅 목적으로 추적하고 싶을 때, useRef는 완벽한 해결책이다.

const renderCount = useRef(0);

useEffect(() => {
  renderCount.current = renderCount.current + 1;
});

// 이 값은 화면에 직접 렌더링하지 않는다.
// 개발자 도구에서 확인하거나, 다른 로직에 활용할 뿐이다.

만약 useState를 썼다면, useEffect 안에서 setRenderCount를 호출하는 순간 무한 리렌더링 루프에 빠졌을 것이다. 일반 변수를 썼다면, 값은 항상 1에서 멈춰 있었을 것이다.

2. 이전 상태 값 저장하기

useRef를 사용하면, 이전 렌더링에서의 상태 값을 쉽게 저장하고 현재 값과 비교할 수 있다.

const [name, setName] = useState('');
const prevName = useRef('');

useEffect(() => {
  // 현재 렌더링이 끝난 후, 현재 name 값을 ref에 저장한다.
  // 그러면 다음 렌더링 때, prevName.current는 이전 name 값을 가지게 된다.
  prevName.current = name;
}, [name]);

return (
  <>
    <input value={name} onChange={e => setName(e.target.value)} />
    <div>My name is {name} and it used to be {prevName.current}</div>
  </>
)

이 패턴은 상태가 어떻게 변했는지 추적하거나, 특정 조건에서만 이펙트를 실행해야 할 때 매우 유용하다.

📜 결론

useRef는 단순히 DOM을 위한 훅이 아니다. 클래스 컴포넌트 시절의 '인스턴스 변수'와 같은 역할을 하는, 함수형 컴포넌트의 강력한 도구이다.

"리렌더링 사이클에 영향을 주지 않으면서, 컴포넌트의 생명주기 동안 어떤 값을 계속해서 기억하고 싶을 때", useRef는 가장 정확하고 효율적인 해답이 되어줄 것이다.

profile
인생은 프레임워크처럼, 공부는 라이브러리처럼

0개의 댓글