카테고리 없음

useRef와 함께 알아야 할 React 훅들

taek2-0310 2026. 5. 17. 22:48

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)가 복잡한 계산을 필요로 할 때