useRef — 정의, 참조 훅 및 React에서 DOM 작업

저자: IT Sectr 게시일: 2026-07-04 읽는 시간: 9 분

useRef는 .current 속성을 가진 가변 ref 객체를 생성하는 React 훅으로, 컴포넌트 렌더링 간에 유지됩니다. useState와 달리 .current를 변경해도 리렌더링이 발생하지 않아, useRef는 DOM 참조, 타이머 및 UI 재렌더링 없이 렌더링 간에 유지해야 하는 모든 값을 저장하는 데 이상적입니다. React 문서(2025)에 따르면, ref 객체는 컴포넌트 수명 동안 한 번 생성되며 렌더링 간에 변경되지 않아 참조 안정성을 보장합니다.

핵심 요점

  • useRef — .current 필드를 가진 가변 ref 객체를 생성하는 훅.
  • 리렌더링 없음 — .current 변경이 컴포넌트 리렌더링을 트리거하지 않습니다.
  • DOM 참조 — DOM 노드에 직접 액세스하기 위해 요소의 ref prop에 전달됩니다.
  • 값 저장 — 타이머, 이전 값 및 렌더링 간 데이터에 적합합니다.
  • 안정적인 참조 — 클로저와 달리 ref 객체는 렌더링 간에 변경되지 않습니다.

React에서 useRef란

useRef는 React 16.8에 추가된 훅으로, 단일 .current 속성을 가진 가변 ref 객체를 반환합니다. 초기 값은 컴포넌트 마운트 시 .current에 할당됩니다. 일반 변수와의 주요 차이점은 ref 객체가 리렌더링에서 살아남는다는 것입니다. 이후 렌더링에서 React는 ref를 다시 생성하지 않고 동일한 객체를 반환합니다.

내부적으로 useRef는 useState 및 useEffect와 동일한 메커니즘(파이버 노드의 훅 연결 리스트)을 사용합니다. 그러나 useState와 달리 React는 ref 객체의 변경을 추적하지 않으며, 업데이트를 대기열에 넣지 않고, 리렌더링을 예약하지 않습니다. 이는 useRef를 빈번한 변경에서도 성능에 영향을 주지 않는 매우 가벼운 훅으로 만듭니다.

React 팀 — Hooks FAQ(2025)에 따르면, useRef는 본질적으로 값을 저장하는 “상자”입니다. 구문 설탕: const ref = useRef(초기값)은 const ref = { current: 초기값 }과 동일하지만, React가 모든 렌더링에서 동일한 ref 객체를 보장한다는 차이가 있습니다. 마법이 아닌 React가 관리하는 안정적인 참조일 뿐입니다.

jsx
import { useRef } from 'react';

function Component() {
    const countRef = useRef(0);
    // countRef.current = 0 초기 상태
    // countRef.current = 5 변경 후
    // 리렌더링이 발생하지 않습니다!
}

useRef로 DOM 요소 액세스

useRef의 가장 일반적인 사용 사례는 DOM 요소에 직접 액세스하는 것입니다. React는 컴포넌트 마운트 후 elementRef.current에 DOM 노드 참조를 할당합니다. 이는 input 포커싱, 요소 크기 측정, 애니메이션 라이브러리 및 React 렌더링을 사용하지 않는 라이브러리와의 통합에 필요합니다.

React는 DOM에서 ref 참조를 자동으로 관리합니다. 마운트 시 실제 DOM 노드를 할당하고, 언마운트 시 null로 설정합니다. 이는 .current가 항상 최신 값을 유지하고, 제거된 요소에 대한 매달린 참조를 통한 메모리 누수를 방지합니다. 수동 정리가 필요하지 않습니다.

jsx
function AutoFocusInput() {
    const inputRef = useRef(null);

    useEffect(() => {
        // 컴포넌트 마운트 후 input 포커스
        inputRef.current?.focus();
    }, []);

    return <input ref={inputRef} type="text" />;
}

React — Refs and DOM 문서(2025)에 따르면, 선언적 작업(모달 열기/닫기, 가시성 관리)에는 ref를 사용하지 마세요. 이러한 목적에는 state와 props가 있습니다. Ref는 선언적으로 표현할 수 없는 명령형 작업(포커스, 텍스트 선택, 타사 라이브러리 통합)을 위한 것입니다.

리렌더링 없이 가변 값 저장

두 번째로 중요한 사용 사례는 렌더링 간에 유지되어야 하지만 변경으로 인해 리렌더링이 트리거되어서는 안 되는 임의 값 저장입니다. 일반적인 예: 타이머 식별자(setInterval/setTimeout), 요청 취소 플래그, 비교를 위한 이전 prop 값 및 UI 출력에 영향을 주지 않는 모든 데이터.

이는 useEffect에서 특히 유용합니다. useRef는 타이머 식별자를 저장하고 클린업이 이를 지웁니다. timerId가 useState에 저장된 경우, 각 setTimerId 호출은 로직이나 UI에 필요하지 않은 불필요한 리렌더링을 발생시킵니다. useRef는 오버헤드 없이 불필요한 리렌더링 없이 이 문제를 해결합니다.

jsx
function Timer() {
    const intervalRef = useRef(null);

    const start = () => {
        intervalRef.current = setInterval(() => {
            console.log('틱');
        }, 1000);
    };

    const stop = () => {
        clearInterval(intervalRef.current);
    };

    useEffect(() => stop, []);

    return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}

useRef vs useState: 각각 사용 시기

주요 딜레마 — useRef와 useState 중 선택 — 은 한 가지 질문으로 해결됩니다: “값이 변경될 때 리렌더링이 필요한가?” 예 — useState. 아니요 — useRef. useState는 컴포넌트 출력에 영향을 주는 상태를 저장합니다. useRef는 내부 로직에 필요하지만 UI에 영향을 주지 않는 데이터를 저장합니다.

실제로 개발자는 클로저 문제를 피하기 위해 콜백을 저장하는 목적으로 useRef를 자주 사용합니다. 예를 들어, useEffect가 이벤트를 구독하고 콜백이 현재 state를 필요로 하는 경우 콜백을 useRef에 저장합니다. 각 렌더링에서 ref.current를 새 함수로 업데이트하면, 효과는 재구독 없이 항상 최신 콜백을 호출합니다.

기준useRefuseState
리렌더링변경 시 트리거 안 함각 setState 시 트리거
변경직접: ref.current = 값세터 통해: setState(값)
JSX 사용사용 안 함 (효과 없음)컴포넌트 출력에서 사용
예시타이머, DOM 참조, 이전 값폼 데이터, UI 상태, 플래그

안티 패턴이 있습니다: JSX에 필요한 데이터에 useRef를 사용하면서 변경으로 인해 리렌더링이 발생하지 않아야 하는 경우입니다. 이는 역동기화를 초래합니다 — ref.current는 이미 새로운데 UI는 오래된 데이터를 표시합니다. 값이 UI에 표시되는 경우 useState를 사용하세요. 내부적으로만 사용되는 경우 useRef를 사용하세요.

useEffect와 useRef 조합

useRef + useEffect 조합은 이전 prop 값을 추적하는 표준 패턴입니다. 이전 값을 ref에 저장하고, useEffect에서 현재 값과 비교한 후 차이에 기반하여 결정을 내립니다. 이는 변경 전의 값을 알아야 하는 애니메이션에서 특히 유용합니다.

jsx
function PriceDisplay({ price }) {
    const prevPriceRef = useRef(price);

    useEffect(() => {
        const prevPrice = prevPriceRef.current;
        if (price > prevPrice) {
            animateUp();
        } else if (price < prevPrice) {
            animateDown();
        }
        prevPriceRef.current = price;
    }, [price]);

    return <span>${price}</span>;
}

React 문서 — Hooks FAQ(2025)에 따르면, 이 패턴을 “이전 값 패턴”이라고 합니다. useRef가 렌더링 간에 값을 유지하고, useEffect가 변경 사항이 커밋된 후 실행되기 때문에 작동합니다. 먼저 DOM이 새 price로 업데이트된 다음, useEffect가 이를 (아직 ref에 있는) 이전 값과 비교하고 ref.current를 현재 값으로 업데이트합니다.

useRef의 일반적인 실수

가장 빈번한 실수는 JSX를 계산하기 위해 렌더링 단계에서 ref.current를 읽는 것입니다. ref.current 변경이 리렌더링을 트리거하지 않으므로 컴포넌트가 오래된 값을 사용할 수 있습니다. .current가 UI 출력에 관여하는 경우 useState를 사용하세요. ref와 state를 동기화해야 하는 경우, useEffect를 사용하여 ref에서 state를 업데이트하세요.

  • JSX의 Ref — 렌더 함수 내에서 ref.current를 읽으면 UI와 데이터의 역동기화가 발생합니다.
  • useEffect 의존성으로서의 Ref — ref.current는 의존성 배열에 포함되어서는 안 됩니다. React는 그 변경을 추적하지 않습니다.
  • null 확인 누락 — 언마운트 시 ref.current가 null이 됩니다. 액세스 전에 확인하세요.
  • ref에 함수 저장 — 최신 콜백 패턴을 사용하는 경우 각 렌더링에서 ref.current를 업데이트하세요.
jsx
// ❌ 표시를 위해 렌더링 중에 ref.current를 읽지 마세요
function BadComponent() {
    const valRef = useRef(0);
    return <p>{valRef.current}</p>; // 변경 시 업데이트되지 않음
}

// ✅ 표시에는 state, 로직에는 ref를 사용하세요
function GoodComponent() {
    const [val, setVal] = useState(0);
    const valRef = useRef(0);
    return <p>{val}</p>;
}

또 다른 일반적인 실수는 데이터가 실제로 UI에 영향을 미치는 경우 컴포넌트에서 상태를 저장하는 유일한 방법으로 useRef를 사용하는 것입니다. 개발자는 “불필요한” 리렌더링을 피하기 위해 useRef를 선택하지만, 결국 업데이트되지 않는 UI를 얻게 됩니다. 올바른 접근 방식: UI 데이터에는 useState를 사용하고, 렌더링에 관여하지 않는 보조 값에만 useRef를 사용하세요.

자주 묻는 질문

이전 상태를 저장하는 데 useRef를 사용할 수 있나요?

네, 일반적인 패턴입니다 — 이전 상태 패턴. ref를 만들고 추적하는 값이 변경될 때마다 useEffect에서 업데이트하세요. 렌더링과 효과 사이에 ref.current에는 이전 값이 포함되어 있으며, 현재 값과 비교할 수 있습니다. 추가 리렌더링이 필요하지 않으며 모든 데이터 타입에서 작동합니다.

첫 번째 렌더링에서 ref.current가 null인 이유는?

React는 요소가 렌더링되어 실제 DOM에 추가된 후에만 ref.current에 DOM 노드 값을 할당합니다. 첫 번째 렌더링 중에는 컴포넌트가 아직 마운트되지 않았으므로 ref.current는 초기 값(null)과 같습니다. ref.current를 통한 DOM 액세스는 useEffect 내에서 또는 마운트 후에 호출되는 이벤트 핸들러에서만 가능합니다.

useRef와 createRef의 차이점은?

createRef는 렌더링마다 새 ref 객체를 생성합니다 — 클래스 컴포넌트에서만 사용해야 합니다. useRef는 ref를 한 번 생성하고 이후 모든 렌더링에서 동일한 객체를 반환합니다. 함수형 컴포넌트에서는 useRef만 사용하세요. createRef는 객체가 다시 생성되므로 리렌더링 시 값 손실을 초래합니다.

useRef를 props로 전달할 수 있나요?

네, ref 객체는 일반 prop으로 전달할 수 있습니다: <Child inputRef={inputRef} />. 자식 컴포넌트는 inputRef.current를 통해 사용합니다. 그러나 ref를 자식 컴포넌트의 DOM 요소에 직접 전달하려면 forwardRef — props를 통해 ref를 전달할 수 있는 HOC를 사용하세요. forwardRef가 없으면 ref는 자식 컴포넌트 내부의 DOM 요소에 자동으로 바인딩되지 않습니다.

useRef로 요소 크기를 측정하는 방법은?

요소에 ref를 바인딩한 다음, useEffect에서 ref.current.getBoundingClientRect() 또는 ref.current.offsetWidth / offsetHeight를 읽으세요. 크기 변경의 반응형 추적을 위해 useEffect 내에서 ResizeObserver를 사용하세요: observer를 생성하고, 변경을 구독하고, 새 치수로 state를 업데이트하세요. 클린업에서 observer 연결을 해제하는 것을 잊지 마세요.

요약

  • useRef — 리렌더링을 트리거하지 않고 렌더링 간에 유지되는 가변 ref 객체를 생성하는 훅.
  • DOM 액세스 — ref prop이 직접 DOM 노드 액세스를 위해 요소에 전달됨. 마운트 후 사용 가능.
  • 데이터 저장 — 타이머, 콜백, 플래그 및 UI에 영향을 주지 않는 모든 값을 ref에 저장.
  • useRef vs useState — 변경 시 UI를 다시 렌더링해야 하면 useState, 아니면 useRef 선택.
  • 이전 값 패턴 — 이전 값을 ref에 저장하고 비교를 위해 useEffect에서 업데이트.
  • JSX의 Ref — 렌더링 단계에서 ref.current를 읽지 마세요. UI와 데이터의 역동기화가 발생합니다.
  • forwardRef — props를 통해 자식 컴포넌트에 ref를 전달하는 데 사용.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기