어제 오늘 내일

[React] useRef 사용법: 렌더링 없이 값을 기억하고 DOM에 접근하기 본문

카테고리 없음

[React] useRef 사용법: 렌더링 없이 값을 기억하고 DOM에 접근하기

hi.anna 2026. 8. 3. 22:17

React를 배우다 보면 useState만으로는 해결하기 애매한 상황을 만나게 됩니다. "값은 기억하고 싶은데 화면은 다시 그리고 싶지 않을 때", 혹은 "특정 input에 직접 포커스를 주고 싶을 때" 같은 경우죠. 이럴 때 사용하는 것이 바로 useRef Hook입니다. 이 글에서는 useRef의 두 가지 핵심 용도를 예제와 함께 알아보겠습니다.

useRef란?

useRef는 변경 가능한(mutable) 값을 담는 상자를 만들어주는 Hook입니다. 이 상자는 .current라는 프로퍼티를 가지며, 여기에 어떤 값이든 담아둘 수 있습니다.

import { useRef } from 'react';

function Component() {
  // 초기값 0을 담은 ref 객체를 생성
  // ref는 { current: 0 } 형태의 객체
  const countRef = useRef(0);

  // 값에 접근할 때는 .current 사용
  console.log(countRef.current); // 0
}

useRef의 가장 중요한 특징은 두 가지입니다.

  1. 값이 바뀌어도 컴포넌트를 리렌더링하지 않는다
  2. 리렌더링이 일어나도 값이 유지된다

이 특징 때문에 useRef는 크게 두 가지 용도로 쓰입니다. 하나씩 살펴보겠습니다.

용도 1: 렌더링과 무관한 값 저장하기

useState와 useRef의 가장 큰 차이는 리렌더링 여부입니다.

import { useState, useRef } from 'react';

function Counter() {
  // useState: 값이 바뀌면 화면이 다시 그려짐
  const [stateCount, setStateCount] = useState(0);

  // useRef: 값이 바뀌어도 화면은 다시 그려지지 않음
  const refCount = useRef(0);

  const handleClick = () => {
    // ref 값은 직접 .current를 수정 (setState 같은 함수 없음)
    refCount.current += 1;
    console.log('ref 값:', refCount.current); // 콘솔에는 증가한 값이 찍힘

    // 하지만 화면의 refCount는 갱신되지 않음 (리렌더링이 안 되니까)
  };

  return (
    <div>
      <p>state: {stateCount}</p>
      <p>ref: {refCount.current}</p>
      <button onClick={handleClick}>ref 증가</button>
    </div>
  );
}

위 예제에서 버튼을 눌러 refCount를 증가시켜도 화면의 ref 값은 바뀌지 않습니다. 콘솔에는 증가한 값이 찍히지만, 리렌더링이 일어나지 않으니 화면은 그대로입니다. stateCount가 바뀌어 리렌더링이 일어나야 비로소 갱신된 ref 값이 화면에 반영됩니다.

이 특성 덕분에 useRef는 화면에 보여줄 필요는 없지만 계속 기억해야 하는 값을 저장하는 데 유용합니다.

function Timer() {
  // setInterval이 반환하는 타이머 ID를 저장
  // 이 값은 화면에 보여줄 필요가 없고, 리렌더링을 유발할 필요도 없음
  const timerId = useRef(null);

  const startTimer = () => {
    // 타이머 ID를 ref에 보관
    timerId.current = setInterval(() => {
      console.log('1초마다 실행');
    }, 1000);
  };

  const stopTimer = () => {
    // 저장해둔 ID로 타이머를 정지
    clearInterval(timerId.current);
  };

  return (
    <div>
      <button onClick={startTimer}>시작</button>
      <button onClick={stopTimer}>정지</button>
    </div>
  );
}

만약 타이머 ID를 useState로 관리했다면, ID가 저장될 때마다 불필요한 리렌더링이 발생했을 것입니다. useRef를 쓰면 이런 낭비 없이 값을 관리할 수 있습니다.

용도 2: DOM 요소에 직접 접근하기

useRef의 또 다른 대표적인 용도는 실제 DOM 요소에 접근하는 것입니다. React에서는 보통 DOM을 직접 다루지 않지만, 포커스 설정이나 스크롤 이동처럼 어쩔 수 없이 직접 접근해야 하는 경우가 있습니다.

import { useRef } from 'react';

function SearchInput() {
  // DOM 요소를 담을 ref 생성 (초기값은 null)
  const inputRef = useRef(null);

  const focusInput = () => {
    // .current에 실제 <input> DOM 요소가 담겨 있음
    // 순수 자바스크립트처럼 focus() 메서드 호출 가능
    inputRef.current.focus();
  };

  return (
    <div>
      {/* ref 속성에 inputRef를 연결하면 */}
      {/* React가 이 input의 실제 DOM을 inputRef.current에 넣어줌 */}
      <input ref={inputRef} type="text" placeholder="검색어 입력" />
      <button onClick={focusInput}>검색창에 포커스</button>
    </div>
  );
}

ref 속성에 ref 객체를 연결하면, React가 렌더링 후 해당 DOM 요소를 자동으로 .current에 넣어줍니다. 그 후에는 inputRef.current.focus()처럼 순수 자바스크립트 DOM API를 그대로 사용할 수 있습니다.

실전 예제: 마운트 시 자동 포커스

컴포넌트가 화면에 나타나자마자 input에 포커스를 주는 것도 자주 쓰이는 패턴입니다.

import { useRef, useEffect } from 'react';

function LoginForm() {
  const inputRef = useRef(null);

  // 컴포넌트가 처음 마운트될 때 한 번 실행
  useEffect(() => {
    // 화면에 나타나자마자 이메일 입력창에 자동 포커스
    inputRef.current.focus();
  }, []); // 빈 배열: 최초 1회만 실행

  return (
    <form>
      <input ref={inputRef} type="email" placeholder="이메일" />
      <input type="password" placeholder="비밀번호" />
    </form>
  );
}

useRef vs useState: 언제 무엇을 쓸까?

두 Hook은 비슷해 보이지만 목적이 다릅니다. 아래 표로 정리했습니다.

구분 useState useRef
값 변경 시 리렌더링 O (일어남) X (일어나지 않음)
값 접근 방법 변수 직접 사용 .current로 접근
값 변경 방법 setState 함수 .current 직접 수정
주요 용도 화면에 반영되는 상태 DOM 접근, 렌더링 무관한 값 저장

핵심 기준은 간단합니다. 그 값이 바뀌었을 때 화면이 다시 그려져야 하는가? 그렇다면 useState, 아니라면 useRef입니다.

주의할 점

1. 렌더링 중에 ref를 읽거나 쓰지 말 것

.current 값은 렌더링 과정(컴포넌트 함수 본문)에서 직접 읽거나 수정하지 않는 것이 원칙입니다. 이벤트 핸들러나 useEffect 안에서 다루는 것이 안전합니다.

function Component() {
  const ref = useRef(0);

  // ❌ 렌더링 중에 ref를 수정하는 것은 예측 불가능한 동작을 유발
  ref.current += 1;

  // ✅ 이벤트 핸들러나 useEffect 안에서 다루기
  const handleClick = () => {
    ref.current += 1;
  };

  return <button onClick={handleClick}>클릭</button>;
}

2. ref 값 변경은 화면에 반영되지 않는다

앞서 강조했듯, ref를 수정해도 화면은 자동으로 갱신되지 않습니다. 화면에 보여줘야 하는 값이라면 반드시 useState를 사용해야 합니다.

마무리

useRef는 React에서 두 가지 뚜렷한 역할을 합니다. 첫째, 리렌더링을 유발하지 않으면서 값을 기억하고 싶을 때(타이머 ID, 이전 값 저장 등), 둘째, 실제 DOM 요소에 직접 접근해야 할 때(포커스, 스크롤 등)입니다.

기억할 핵심은 이것입니다. useState는 화면을 위한 상태이고, useRef는 화면 뒤에서 조용히 값을 기억하거나 DOM을 다루기 위한 도구입니다. 이 둘의 차이를 명확히 이해하면 React 컴포넌트를 훨씬 정교하게 제어할 수 있습니다.

반응형
Comments