React, Vue

React Hooks ✅ useMemo, useCallback, useRef 리렌더링 최적화

jonbeo 2025. 12. 10. 10:05
반응형

 

 

React 앱이 느려지는 주요 원인은 대부분 “불필요한 리렌더링”입니다.
컴포넌트가 재렌더링될 때마다 함수, 객체, 배열이 새로 생성되기 때문이죠.

 

이 문제를 해결하기 위한 세 가지 Hook:

🧠 useMemo, ⚙️ useCallback, 📦 useRef

 

이 세 가지를 제대로 알면 성능 최적화 + 안정적인 상태 관리가 동시에 가능합니다 👇


🧠 1. useMemo – 계산 결과를 메모이징

복잡한 계산의 결과를 기억해두고, 의존값이 변할 때만 다시 계산.

 
import { useMemo, useState } from "react";

function ExpensiveComponent({ items }: { items: number[] }) {
  const [count, setCount] = useState(0);

  const total = useMemo(() => {
    console.log("복잡한 계산 실행...");
    return items.reduce((a, b) => a + b, 0);
  }, [items]);

  return (
    <div>
      <p>합계: {total}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

 

📌 items가 변경되지 않는 한 reduce()는 다시 실행되지 않음
렌더링 시 불필요한 연산 절감

 

✅ 사용 시점

  • 무거운 연산 (정렬, 필터, 계산 등)
  • 동일한 입력에 동일한 결과 반환되는 함수

⚙️ 2. useCallback – 함수 재생성 방지

함수를 메모이징해서, 불필요한 자식 컴포넌트 리렌더링 방지.

 
import { useCallback, useState } from "react";

function Button({ onClick }: { onClick: () => void }) {
  console.log("버튼 렌더링");
  return <button onClick={onClick}>클릭</button>;
}

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

  const handleClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <>
      <p>Count: {count}</p>
      <Button onClick={handleClick} />
    </>
  );
}

 

📌 일반 함수로 작성하면 매 렌더마다 새로운 함수 객체가 생성되어
Button이 매번 리렌더링됨.
useCallback으로 방지 ✅

 

✅ 사용 시점

  • 자식 컴포넌트에 콜백 전달 시
  • 이벤트 핸들러, useEffect 의존성 배열에 들어갈 때

📦 3. useRef – 렌더링에 영향 없는 데이터 저장소

DOM 접근, 이전 값 저장, 변경값 추적 등에 사용.

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

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef<number | null>(null);

  useEffect(() => {
    intervalRef.current = window.setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);
    return () => clearInterval(intervalRef.current!);
  }, []);

  return <p>{count}초 경과</p>;
}

 

📌 useRef는 렌더링을 일으키지 않음
📌 변경해도 UI 리렌더링이 발생하지 않아 성능 부담 없음

 

✅ 사용 시점

  • DOM 요소 접근 (ref.current.focus())
  • setInterval, setTimeout 관리
  • 이전 값 저장 (prevValue.current = value)

⚡ 4. useMemo vs useCallback 차이

Hook 메모이징 대상 반환값 주 사용 사례
useMemo 계산 결과 값(value) 복잡한 계산 최적화
useCallback 함수 함수(fn) 이벤트 핸들러 캐싱

📌 useCallback(fn, deps) 은 사실상 useMemo(() => fn, deps)와 동일


🧩 5. 세 Hook을 함께 사용하는 실무 예시

 
import { useMemo, useCallback, useRef, useState } from "react";

function SearchList({ list }: { list: string[] }) {
  const [query, setQuery] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  const filtered = useMemo(
    () => list.filter((item) => item.includes(query)),
    [list, query]
  );

  const focusInput = useCallback(() => {
    inputRef.current?.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={focusInput}>검색창 포커스</button>
      <ul>
        {filtered.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

 

📌 3대 Hook 종합 예제

  • useMemo → 필터링 최적화
  • useCallback → 버튼 클릭 이벤트 캐싱
  • useRef → DOM 접근

🧠 6. 성능 최적화 실무 팁

✅ 무조건 감싸지 말기 (불필요한 메모이징은 오히려 손해)
✅ useMemo는 “비싼 연산”만 캐싱
✅ useCallback은 “자식 리렌더링 방지”에만 사용
✅ useRef는 상태 대신 “렌더링 영향 없는 값” 저장


📝 마무리 정리

  • useMemo → 연산 결과 캐싱
  • useCallback → 함수 캐싱
  • useRef → DOM 및 불변 데이터 저장

👉 이 세 가지를 적절히 쓰면
불필요한 렌더링을 60~80% 줄이고, UX 반응성도 크게 개선됩니다 ⚡

반응형