1. useRef — "렌더링 없이 값을 기억하고 싶을 때"
useRef는 두 가지 역할을 한다.
① 렌더링을 유발하지 않는 변경 가능한 값 저장소
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log(countRef.current); // 렌더링 없이 값 업데이트
};
구분useStateuseRef
| 값 변경 시 렌더링 | O | X |
| 렌더링 간 값 유지 | O | O |
| 용도 | UI에 반영할 상태 | 내부적으로만 필요한 값 |
② DOM 엘리먼트 직접 참조
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // DOM API 직접 호출
};
return <input ref={inputRef} />;
언제 useRef를 쓸까?
- 이전 렌더링의 props/state 값을 기억할 때
- setInterval / setTimeout ID 저장할 때
- 애니메이션 프레임 ID 관리할 때
- DOM 엘리먼트에 직접 접근해야 할 때
2. useCallback — "함수 참조가 바뀌면 안 될 때"
useCallback은 함수를 메모이제이션한다. 의존성 배열이 바뀌지 않으면 이전 렌더링에서 생성된 동일한 함수 참조를 반환한다.
// ❌ 매 렌더링마다 새로운 함수 생성
const handleSubmit = () => {
sendData(inputValue);
};
// ✅ inputValue가 바뀔 때만 새로운 함수 생성
const handleSubmit = useCallback(() => {
sendData(inputValue);
}, [inputValue]);
useRef와의 관계: useCallback은 내부적으로 useRef와 유사한 메커니즘으로 이전 값을 기억한다. 직접 조합하면 의존성 없이 최신 값을 참조하는 안정적인 콜백을 만들 수 있다.
// 안정적인 콜백 패턴 (useRef + useCallback 조합)
const callbackRef = useRef(callback);
callbackRef.current = callback;
const stableCallback = useCallback((...args) => {
return callbackRef.current(...args);
}, []); // 의존성 배열이 비어있어도 항상 최신 callback 호출
언제 useCallback을 쓸까?
- React.memo로 감싼 자식 컴포넌트에 함수를 prop으로 전달할 때
- useEffect의 의존성 배열에 함수가 들어갈 때
- 함수 생성 비용이 클 때 (드문 경우)
남용 주의: 단순한 함수에 useCallback을 무조건 감싸면 오히려 메모리와 성능에 부담이 된다.
3. useMemo — "계산 결과를 재사용하고 싶을 때"
useMemo는 값을 메모이제이션한다. useCallback이 함수를 캐싱한다면, useMemo는 함수의 반환값을 캐싱한다.
// ❌ 렌더링마다 무거운 계산 반복
const sortedList = items.sort((a, b) => b.score - a.score);
// ✅ items가 바뀔 때만 재계산
const sortedList = useMemo(() => {
return items.sort((a, b) => b.score - a.score);
}, [items]);
useCallback vs useMemo 비교:
// 아래 두 표현은 동일하다
const memoizedFn = useCallback(fn, deps);
const memoizedFn = useMemo(() => fn, deps);
구분useCallbackuseMemo
| 메모이제이션 대상 | 함수 자체 | 함수의 반환값 |
| 반환 타입 | 함수 | 임의의 값 |
| 주요 용도 | 콜백 안정화 | 계산 최적화 |
언제 useMemo를 쓸까?
- 큰 배열의 필터링/정렬 같은 무거운 연산을 캐싱할 때
- 객체/배열을 생성해 자식 컴포넌트에 prop으로 전달할 때 (참조 동일성 유지)
- 파생 상태(derived state)가 복잡한 계산을 필요로 할 때