React Refs & useRef — DOM에 대한 ‘비밀 백도어’ 🚪
Source: Dev.to
React에서 DOM 요소에 직접 접근해야 했지만, React가 방해하는 것처럼 느껴진 적 있나요?
바로 그때 useRef가 필요한 것입니다. 이를 실제 DOM에 손을 뻗을 수 있게 해주는 비밀 뒷문이라고 생각하세요 — React의 규칙을 어기지 않으면서.
State vs. Ref — 두 문장 버전
State → 변경이 발생하면 다시 렌더링됩니다. setter 함수를 사용해 업데이트합니다.
Ref → 변경이 조용히 일어납니다. 직접 변형하고, React는 전혀 반응하지 않습니다.
Refs는 자신을 위해 붙이는 메모와 같습니다. React는 그 메모에 무엇을 적었는지는 신경 쓰지 않습니다.
Ref 만들기
import { useRef } from "react";
function MyComponent() {
const inputRef = useRef(null);
return <input ref={inputRef} />;
}
무슨 일이 일어나는가
useRef(null)은 객체를 생성한다:{ current: null }.- 그 객체가
<input>의refprop에 전달된다. - React가
inputRef.current에 해당 입력 요소의 실제 DOM 노드를 채워 넣는다.
이제 inputRef.current는 페이지에 있는 실제 <input> 요소이다.
실제 예시: 새 콘텐츠 자동 스크롤
import { useRef, useEffect, useState } from "react";
function RecipeApp() {
const [recipe, setRecipe] = useState(null);
const recipeSectionRef = useRef(null);
async function fetchRecipe() {
const response = await getRecipeFromAI(); // pretend API call
setRecipe(response);
}
useEffect(() => {
if (recipe && recipeSectionRef.current) {
recipeSectionRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [recipe]);
return (
<div>
<button onClick={fetchRecipe}>Get a Recipe</button>
{recipe && (
<section ref={recipeSectionRef}>
<h2>{recipe.title}</h2>
<p>{recipe.instructions}</p>
</section>
)}
</div>
);
}
단계별로 일어나는 일
- 사용자가 Get a Recipe 버튼을 클릭합니다.
- API가 데이터를 반환 → 상태가 업데이트 → React가 다시 렌더링됩니다.
ref가 연결된<section>이 이제 DOM에 존재합니다.useEffect가 실행되어 레시피가 로드된 것을 확인하고scrollIntoView()를 호출합니다.- 브라우저가 부드럽게 레시피 섹션까지 스크롤됩니다.
document.getElementById도 없고, 쿼리 셀렉터도 없습니다. 깔끔한 ref만 사용합니다.
“하지만 ID만 사용하면 안 될까?”
You could do this:
...
document.getElementById("recipe-section").scrollIntoView();
It works… until it doesn’t. React encourages reusable components. Rendering the same component twice would create duplicate IDs, which is invalid HTML and a source of bugs.
Refs avoid this entirely because they’re scoped to each component instance. Two instances → two separate refs → zero conflicts.
정신 모델 요약 시트
| State | Ref | |
|---|---|---|
| 다시 렌더링을 트리거합니까? | 예 | 아니오 |
| 업데이트 방법 | setter 함수 | 직접 변이 (myRef.current = …) |
| 일반적인 사용 | UI 데이터 | DOM 접근, 타이머, 이전 값 |
| 형태 | 설정한 모든 것 | { current: value } |
기억해야 할 세 가지 간단한 규칙
-
Refs는 단순히 상자일 뿐입니다.
useRef(initialValue)는{ current: initialValue }를 반환합니다 –current라는 하나의 선반을 가진 상자입니다. -
자유롭게 변형하세요.
state와 달리,myRef.current = "whatever"와 같이 할당해도 재렌더링을 일으키지 않습니다. -
refprop은 마법과 같습니다 — 하지만 네이티브 요소에만 적용됩니다.
<input ref={myRef}>는 React가myRef.current에 해당 DOM 노드를 채워 넣게 합니다.
<MyComponent ref={myRef}>는forwardRef를 사용하지 않는 한 단순히ref라는 일반 prop을 전달할 뿐입니다.
TL;DR
useRef는 지속적인 변경 가능한 컨테이너를 생성합니다:{ current: value }..current를 변경해도 리렌더링이 발생하지 않습니다.refprop을 통해 DOM 요소에 연결하면 해당 노드에 직접 접근할 수 있습니다.- 스크롤링, 입력 포커스 지정, 요소 측정, 혹은 렌더링 사이에 값을 저장하면서 업데이트를 트리거하지 않아야 할 때 이상적입니다.
Refs는 처음에 어색하게 느껴질 수 있지만, 한 번 “이해”하면 두 번째 천성처럼 자연스럽습니다. DOM에 직접 핸들을 잡거나 렌더링 사이에 안정적인 변경 가능한 값을 필요로 할 때 사용하세요.