3 분 소요

개요

useRef, useCallback, useMemo는 성능 최적화와 DOM 접근을 위한 Hook입니다. 처음에는 useStateuseEffect만으로도 충분하지만, 앱이 커지면 이 Hook들이 필요합니다.


useRef

DOM 요소 직접 접근

import { useRef } from "react";

function App() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFocus = () => {
    inputRef.current?.focus();   // DOM 메서드 직접 호출
  };

  return (
    <div>
      <input ref={inputRef} placeholder="입력창" />
      <button onClick={handleFocus}>포커스 이동</button>
    </div>
  );
}

리렌더링 없이 값 유지

useState와 다르게 값이 변경되어도 컴포넌트가 다시 렌더링되지 않습니다.

import { useState, useRef, useEffect } from "react";

function Timer() {
  const [display, setDisplay] = useState("0.0s");
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const startTimeRef = useRef<number>(0);

  const start = () => {
    startTimeRef.current = Date.now();
    intervalRef.current = setInterval(() => {
      const elapsed = (Date.now() - startTimeRef.current) / 1000;
      setDisplay(`${elapsed.toFixed(1)}s`);
    }, 100);
  };

  const stop = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };

  return (
    <div>
      <p>{display}</p>
      <button onClick={start}>시작</button>
      <button onClick={stop}>정지</button>
    </div>
  );
}

useState vs useRef 비교

특징 useState useRef
값 변경 시 리렌더링 ✅ 발생 ❌ 발생 안 함
렌더링에 값 반영 ❌ (직접 읽어야 함)
주 용도 UI에 표시할 상태 DOM 접근, 렌더링 무관 값


useCallback

함수 메모이제이션

import { useState, useCallback } from "react";
import React from "react";

// React.memo로 감싼 자식 컴포넌트
const Button = React.memo(({ onClick, label }: { onClick: () => void; label: string }) => {
  console.log(`${label} 버튼 렌더링`);
  return <button onClick={onClick}>{label}</button>;
});

function App() {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(0);

  // ❌ useCallback 없이: 매 렌더링마다 새 함수 생성 → Button 항상 리렌더링
  // const increment = () => setCount((c) => c + 1);

  // ✅ useCallback: count가 변하지 않으면 같은 함수 재사용 → Button 리렌더링 없음
  const increment = useCallback(() => {
    setCount((c) => c + 1);
  }, []);   // 의존성 없음 → 최초 1회만 생성

  return (
    <div>
      <p>count: {count}, other: {other}</p>
      <Button onClick={increment} label="카운트 증가" />
      <button onClick={() => setOther((o) => o + 1)}>other 증가</button>
    </div>
  );
}


useMemo

계산 결과 메모이제이션

import { useState, useMemo } from "react";

function App() {
  const [numbers] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  const [filter, setFilter] = useState<"all" | "even" | "odd">("all");
  const [other, setOther] = useState(0);

  // ❌ useMemo 없이: other가 바뀔 때도 매번 계산 반복
  // const filtered = numbers.filter(...)

  // ✅ useMemo: filter가 변경될 때만 재계산
  const filtered = useMemo(() => {
    console.log("필터 계산 실행");
    return numbers.filter((n) => {
      if (filter === "even") return n % 2 === 0;
      if (filter === "odd") return n % 2 !== 0;
      return true;
    });
  }, [numbers, filter]);

  return (
    <div>
      <div>
        {(["all", "even", "odd"] as const).map((f) => (
          <button key={f} onClick={() => setFilter(f)}>{f}</button>
        ))}
      </div>
      <p>{filtered.join(", ")}</p>
      <button onClick={() => setOther((o) => o + 1)}>other: {other}</button>
    </div>
  );
}

객체/배열 안정화

import { useState, useMemo } from "react";
import React from "react";

const Child = React.memo(({ config }: { config: { color: string } }) => {
  console.log("Child 렌더링");
  return <div style=>텍스트</div>;
});

function Parent() {
  const [text, setText] = useState("");

  // ❌ 매 렌더링마다 새 객체 생성 → Child 항상 리렌더링
  // const config = { color: "red" };

  // ✅ useMemo로 동일 객체 유지 → text 변경 시 Child 리렌더링 안 함
  const config = useMemo(() => ({ color: "red" }), []);

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <Child config={config} />
    </div>
  );
}


언제 사용해야 하나?

Hook 사용 상황 사용 안 해도 되는 경우
useRef DOM 직접 조작, 렌더링 무관 값 저장 일반 상태 관리
useCallback 자식에게 함수 Props 전달 + React.memo 사용 자식이 React.memo 없을 때
useMemo 계산 비용이 클 때, 객체/배열 Props 안정화 단순 계산

성능 최적화는 문제가 실제로 생겼을 때 적용하기. 모든 곳에 사용하면 오히려 코드가 복잡해집니다.


관련 링크