Frontend/React
useRef 완벽 가이드: DOM 접근부터 데이터 저장까지
미니임
2026. 2. 25. 00:16

React로 개발을 하다 보면 선언적인 방식(Declarative)만으로는 해결하기 어려운 상황이 발생합니다. 특정 입력창에 포커스를 주거나, 스크롤 위치를 계산하거나, 혹은 리렌더링과 상관없이 값을 유지해야 할 때가 바로 그때입니다.
오늘은 React의 핵심 훅 중 하나인 useRef의 두 가지 주요 용도와 실전 예제를 살펴보겠습니다.
1. useRef란 무엇인가?
useRef는 이름 그대로 **참조(Reference)**를 만들기 위한 훅입니다. 이 훅은 .current라는 단일 속성을 가진 변경 가능한 객체를 반환합니다.
주요 특징
- 리렌더링을 유발하지 않음: useRef의 값이 변해도 컴포넌트는 다시 그려지지 않습니다.
- 저장 공간: 컴포넌트의 전 생애주기 동안 값이 유지됩니다.
- DOM 접근: 실제 HTML 요소에 직접 접근할 수 있는 통로 역할을 합니다.
2. DOM 요소 직접 조작하기 (포커스 제어)
가장 대표적인 사용 사례는 특정 DOM 노드에 포커스를 주거나 애니메이션을 직접 제어하는 것입니다.
예제: 로그인 폼 자동 포커스
페이지가 로드되자마자 아이디 입력창에 포커스가 가도록 하거나, 버튼 클릭 시 특정 입력창으로 이동하는 기능을 구현해 보겠습니다.
import React, { useRef, useEffect } from 'react';
function LoginForm() {
const idInputRef = useRef(null);
const pwInputRef = useRef(null);
// 1. 컴포넌트가 마운트될 때 ID 입력창에 포커스
useEffect(() => {
idInputRef.current.focus();
}, []);
const handleNext = () => {
// 2. ID 입력 후 특정 동작 시 PW 입력창으로 포커스 이동
pwInputRef.current.focus();
};
return (
<div style={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
<h2>로그인</h2>
<input
ref={idInputRef}
type="text"
placeholder="아이디를 입력하세요"
/>
<input
ref={pwInputRef}
type="password"
placeholder="비밀번호를 입력하세요"
/>
<button onClick={handleNext}>비밀번호 입력하러 가기</button>
</div>
);
}
export default LoginForm;
3. 변수 저장소로 활용하기 (Mutable Ref)
useState는 값이 바뀔 때마다 컴포넌트를 다시 렌더링합니다. 하지만 렌더링과 상관없이 내부적으로 값을 기억해야 할 때 useRef는 훌륭한 저장소가 됩니다.
예제: 타이머 ID 관리
setInterval이나 setTimeout을 사용할 때 반환되는 ID 값을 저장하여 나중에 중지(Clear)할 때 사용합니다.
import React, { useState, useRef } from 'react';
function StopWatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
// 리렌더링을 유발하지 않고 타이머 ID를 저장
const timerRef = useRef(null);
const handleStart = () => {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(timerRef.current);
timerRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
};
const handleStop = () => {
clearInterval(timerRef.current);
};
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<div style={{ textAlign: 'center' }}>
<h1>경과 시간: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>시작</button>
<button onClick={handleStop}>중지</button>
</div>
);
}
4. 실전 활용: 스크롤 애니메이션 제어
특정 버튼을 눌렀을 때 특정 섹션으로 부드럽게 스크롤하는 기능은 useRef가 없으면 구현하기 까다롭습니다.
import React, { useRef } from 'react';
function ScrollDemo() {
const sectionRef = useRef(null);
const scrollToSection = () => {
sectionRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
};
return (
<div>
<nav style={{ position: 'fixed', top: 0, background: '#fff', width: '100%' }}>
<button onClick={scrollToSection}>아래 섹션으로 이동</button>
</nav>
<div style={{ height: '150vh', paddingTop: '50px' }}>
<p>스크롤을 내려보거나 위 버튼을 클릭하세요.</p>
</div>
<div
ref={sectionRef}
style={{ height: '500px', background: '#f0f0f0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<h2>짠! 여기에 도착했습니다.</h2>
</div>
</div>
);
}
5. useState vs useRef 비교 요약
특징useStateuseRef
| 반환값 | [상태, 세터함수] | { current: 값 } |
| 리렌더링 여부 | 값이 변하면 리렌더링 됨 | 값이 변해도 리렌더링 안 됨 |
| 주 용도 | UI에 반영해야 하는 데이터 | DOM 접근, UI와 무관한 변수 관리 |
| 값의 동기화 | 비동기적으로 업데이트됨 | 즉시 업데이트됨 |
6. 주의사항
- 렌더링 도중 ref.current를 쓰거나 읽지 마세요.
- React는 렌더링 도중 DOM 노드가 생성되거나 변경될 수 있음을 가정합니다. ref는 항상 useEffect 안이나 이벤트 핸들러 안에서 사용하는 것이 안전합니다.
- 과도한 사용 금지.
- 대부분의 UI 로직은 Props와 State로 해결하는 것이 좋습니다. useRef는 명령적인(Imperative) 방식이므로 꼭 필요한 경우에만 사용하세요.
useRef를 적재적소에 활용하면 React의 선언적 한계를 극복하고 훨씬 유연한 컴포넌트를 설계할 수 있습니다.
반응형