본문 바로가기

리액트

⭐ 5편 — useRef 완전 정복: DOM 제어부터 값 저장까지 실무에서 자주 쓰는 패턴 총정리

⭐ 5편 — useRef 완전 정복: DOM 제어부터 값 저장까지 실무에서 자주 쓰는 패턴 총정리


리액트를 배우다 보면 useRef는 useState나 useEffect보다 존재감이 약합니다.
하지만 실제 프로젝트를 해보면 useRef는 "알아두면 개발 속도가 확 올라가는 실전 훅"이라는 걸 깨닫게 됩니다.
많은 개발자들이 처음에는 useRef의 용도를 “DOM을 조작할 때 쓰는 훅” 정도로만 알고 있지만,
실제로는 더 다양한 역할을 합니다.
이번 글에서는 useRef의 모든 핵심 개념을 쉽게 이해하고 바로 실전에 쓸 수 있도록 정리해드릴게요.


✅ 1. useRef란 무엇인가?

useRef는 리액트에서 값을 저장하거나 DOM 요소에 접근하기 위한 훅입니다.
const ref = useRef(null);
여기서 ref는 current라는 속성 하나를 가지고 있습니다.
ref.current
이 값은 변경되어도 리렌더링을 일으키지 않습니다.
이게 useRef의 가장 큰 특징입니다.


🔥 useRef 핵심 한 줄 요약

✔ useRef는 "변해도 렌더링이 필요 없는 값"을 저장하는 데 최적이다.


✅ 2. useRef의 주요 사용 용도 3가지

useRef는 크게 세 가지 분야에서 위력을 발휘합니다.
✔ 1) DOM 요소 직접 접근 (포커스 이동, 스크롤 조작 등)
HTML 요소를 직접 선택해야 하는 상황에서 자주 사용됩니다.
가장 대표적인 예가 입력창 자동 포커스입니다.
예제
function Login() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="아이디를 입력하세요" />;
}
브라우저에서 직접 document.querySelector를 사용할 필요 없이
리액트 방식으로 DOM 접근이 가능합니다.


✔ 2) 리렌더링 없이 값 저장 (상태 보관)
state는 값이 바뀌면 반드시 리렌더링이 발생하지만,
ref는 값이 바뀌어도 렌더링에 영향을 주지 않습니다.
따라서
이전 값 저장
타이머 ID 저장
데이터 변경 기록
등 “화면을 바꿀 필요는 없지만 값을 기억해야 하는 상황”에 아주 유용합니다.
예: 이전 값 기억하기
function NameTracker({ name }) {
const prevName = useRef("");
useEffect(() => {
prevName.current = name;
}, [name]);
return (
<p>이전 이름: {prevName.current}</p>
);
}


✔ 3) setTimeout, setInterval ID 저장
타이머 ID를 state에 저장하면 렌더링이 발생해 구조가 꼬일 수 있습니다.
ref를 사용하면 깔끔하게 해결됩니다.
const timerRef = useRef(null);
const start = () => {
timerRef.current = setInterval(() => {
console.log("running...");
}, 1000);
};
const stop = () => {
clearInterval(timerRef.current);
};


🔍 state와 useRef의 차이가 헷갈린다면?

아래 표로 쉽게 비교할 수 있습니다.
특성 state useRef
값 변경 시 리렌더링 ✔ 발생함 ❌ 발생하지 않음
값 저장 용도 UI 변화가 필요한 값 UI 변화가 필요 없는 값
대표 사례 카운터, 폼 입력값 DOM 접근, 이전 값 저장, 타이머
이 차이를 이해하면 “어떤 값은 state에 두고, 어떤 값은 ref에 둬야 할지” 명확해집니다.


🔥 실전에서 자주 쓰는 useRef 패턴 3가지

이 부분은 실제 프로젝트에서 특히 많이 쓰이는 패턴입니다.
✔ 패턴 1) 스크롤 조작 (채팅창, 로그 화면 등)
const messageEndRef = useRef(null);
useEffect(() => {
messageEndRef.current.scrollIntoView({ behavior: "smooth" });
}, [messages]);
return <div ref={messageEndRef}></div>;
신규 메시지가 올 때마다 자동으로 스크롤이 내려가는 구조입니다.
✔ 패턴 2) 비밀번호 입력 후 자동 포커스 이동
회원가입 폼에서 자주 사용되는 패턴입니다.
const pwRef = useRef(null);
const handleNext = () => {
pwRef.current.focus();
};
✔ 패턴 3) 렌더링 사이에서도 지속되는 값 관리
예를 들어, API 호출 횟수를 기록해야 할 때:
const apiCounter = useRef(0);
useEffect(() => {
apiCounter.current += 1;
console.log(`API 호출 횟수: ${apiCounter.current}`);
});
만약 state로 관리했다면 호출할 때마다 화면이 재렌더링됩니다.
ref는 이를 피할 수 있어 성능적으로 유리합니다.


🚨 초보자가 useRef에서 자주 하는 실수 3가지

❌ 1) ref.current를 직접 리렌더링 트리거라고 착각
ref는 UI와 연결되지 않습니다.
값이 바뀌어도 화면은 갱신되지 않습니다.
❌ 2) DOM 접근을 과하게 사용
리액트는 선언적인 UI 프레임워크입니다.
DOM 조작은 꼭 필요한 경우에만 사용해야 합니다.
❌ 3) useRef를 상태관리 대체제로 사용하려고 함
ref는 렌더링을 트리거하지 않기 때문에
입력값처럼 화면에 바로 반영되어야 하는 값은 state를 사용해야 합니다.


✨ 마무리: useRef는 실무에서 꼭 필요한 필수 도구다

useRef는 단순한 “DOM 접근 훅”이 아니라,
리렌더링과 무관한 데이터를 안전하게 저장하고
컴포넌트 생명주기 동안 유지해야 하는 값을 관리할 때 매우 강력한 도구입니다.
이 능력을 제대로 활용하면
실무에서 발생하는 복잡한 UI 문제들을 훨씬 쉽게 해결할 수 있습니다.
다음 글에서는
리렌더링과 최적화 개념 — React.memo, useMemo, useCallback
이 부분을 깊이 있게 다뤄드릴게요.
리액트 성능 튜닝은 애드센스 승인 글에서 ‘전문성 점수’를 크게 올릴 수 있는 강력한 주제입니다.