
useRef는 리렌더링 간에도 유지되는 가변 저장소를 제공한다. 값 변경이 렌더를 유발하지 않는다는 점에서 useState와 구분되며, 컴포넌트 인스턴스마다 독립적으로 유지된다는 점에서 단순 let과도 다르다. 주로 DOM 접근, 타이머/외부 핸들 저장, 최신 값 보관 등 렌더와 무관한 정보를 관리할 때 사용한다.
ref.currentuseState vs useRef vs letuseRef<T>(initial)는 { current: T } 형태의 객체를 반환한다.ref.current를 변경해도 컴포넌트는 리렌더링되지 않는다.ref 객체가 유지된다.| 항목 | useState | useRef | let(컴포넌트 내부) |
|---|---|---|---|
| 렌더 트리거 | 값 변경 시 렌더 | 렌더 없음 | 렌더마다 초기화됨 |
| 값 유지(렌더 간) | O | O | X |
| 인스턴스별 독립성 | O | O | O(내부이지만 매 렌더 초기화) |
| 주 용도 | UI에 반영돼야 하는 상태 | 렌더와 무관한 가변 값, DOM 핸들 | 순수 계산용 임시 변수 |
주의: 컴포넌트 “외부 모듈 스코프”에 let을 두면 모든 인스턴스가 공유하므로 권장하지 않는다.
import { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef<HTMLInputElement | null>(null);
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>포커스</button>
</div>
);
}
import { useEffect, useRef } from 'react';
export default function Ticker() {
const intervalId = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
intervalId.current = setInterval(() => {
console.log('tick');
}, 1000);
return () => {
if (intervalId.current) clearInterval(intervalId.current);
};
}, []);
return <div>running...</div>;
}
import { useRef } from 'react';
export default function ClickCounter() {
const clicks = useRef(0);
return (
<button onClick={() => { clicks.current += 1; console.log(clicks.current); }}>
클릭(화면 숫자는 안 바뀜)
</button>
);
}
import { useEffect, useRef } from 'react';
export function useLatest<T>(value: T) {
const ref = useRef(value);
ref.current = value;
return ref; // 항상 최신 값을 가리킴
}
export default function Worker({ onMessage }: { onMessage: (s: string) => void }) {
const latestHandler = useLatest(onMessage);
useEffect(() => {
const id = setInterval(() => {
latestHandler.current('ping'); // 최신 핸들러 호출
}, 1000);
return () => clearInterval(id);
}, []);
return null;
}
ref.current로 뷰를 업데이트하려 하지 말 것. 뷰에 반영돼야 하는 값은 useState로 관리한다.ref.current를 변경해 렌더 결과에 의존하는 로직을 만들지 말 것. 사이드이펙트는 이펙트 훅에서 처리한다.let으로 컴포넌트 간 상태를 공유하지 말 것(인스턴스 간 간섭 발생).ref 남용으로 상태 추적이 어려워지지 않도록, “UI에 영향”이면 state, “렌더 무관”이면 ref를 기본 규칙으로 삼는다.useState를 사용한다.useRef를 사용한다.useRef로 보관하고 이펙트에서 관리한다.let로 상태를 공유하고 있지 않은가? 인스턴스 격리를 보장하도록 수정한다.useRef는 “렌더와 분리된 가변 값”을 안전하게 보존하는 도구다. DOM 접근, 타이머·외부 핸들 저장, 최신 콜백 유지 등에서 특히 유용하다. 반대로 뷰에 영향을 주는 값은 useState로 관리해 예측 가능한 렌더 사이클을 유지하는 것이 바람직하다.