본문 바로가기
일상이 개발

React.memo + useCallback 조합으로 컴포넌트 리렌더링을 막아보자!

by 디어노미 2025. 4. 5.
반응형

🧠 React.memo + useCallback 조합으로 컴포넌트 리렌더링을 막아보자!

React 개발을 하다 보면 종종 이런 경험 해보셨을 거예요.

“버튼 하나 눌렀을 뿐인데, 자식 컴포넌트가 자꾸 리렌더링돼요… 😫”

그럴 때 사용하는 두 가지 무기! 바로 React.memouseCallback입니다.
이번 글에서는 이 둘을 언제, 왜, 어떻게 조합해서 써야 하는지를 쉽고 정확하게 알려드릴게요.


🧩 기본 개념부터 다시 정리

✅ React.memo

함수형 컴포넌트를 메모이제이션해서, props가 바뀌지 않으면 리렌더링을 막는 고차 컴포넌트(HOC)입니다.

✅ useCallback

함수를 메모이제이션해서, 참조값(reference)이 변경되지 않도록 해주는 React 훅이에요.

이 둘을 조합하면? 자식 컴포넌트에게 콜백을 props로 넘길 때 리렌더링을 방지할 수 있습니다! 🎉


💥 문제 상황 예시: 함수를 props로 넘기면?

// 자식 컴포넌트
const Child = React.memo(({ onClick }) => {
  console.log("Child 렌더링");
  return <button onClick={onClick}>클릭</button>;
});

// 부모 컴포넌트
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log("버튼 클릭!");
  };

  return (
    <>
      <Child onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>부모 증가</button>
    </>
  );
}

문제는 여기서 부모 컴포넌트가 리렌더링될 때마다 handleClick 함수도 새로 생성된다는 거예요.
그 결과 React.memo가 props가 바뀌었다고 판단하고 자식도 함께 리렌더링되죠.


✅ 해결책: useCallback으로 함수 메모이제이션

const handleClick = useCallback(() => {
  console.log("버튼 클릭!");
}, []);

이렇게 작성하면 handleClick 함수가 항상 같은 참조를 유지하게 됩니다.
이제 자식 컴포넌트는 진짜 props가 바뀔 때만 리렌더링되죠!

전체 코드 예시:

const Child = React.memo(({ onClick }) => {
  console.log("Child 렌더링");
  return <button onClick={onClick}>클릭</button>;
});

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

  const handleClick = useCallback(() => {
    console.log("버튼 클릭!");
  }, []);

  return (
    <>
      <Child onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>부모 증가</button>
    </>
  );
}

Parent의 state(count)가 바뀌어도 Child는 리렌더링되지 않습니다! 🎯


📌 실무에서 자주 쓰는 패턴

  • 자식 컴포넌트가 props로 콜백을 받는 경우
  • 렌더링 비용이 큰 리스트 요소
  • React.memo로 감싼 컴포넌트가 불필요하게 리렌더링되는 걸 막고 싶을 때

예: 쇼핑몰에서 상품 카드 클릭 처리, 토글 버튼 등


📚 useCallback + React.memo 시리즈 패턴

// 자식 컴포넌트
const ProductItem = React.memo(({ product, onAddToCart }) => {
  console.log("ProductItem 렌더링");
  return (
    <div>
      <p>{product.name}</p>
      <button onClick={() => onAddToCart(product.id)}>담기</button>
    </div>
  );
});

// 부모 컴포넌트
function ProductList({ products }) {
  const [cart, setCart] = useState([]);

  const handleAdd = useCallback((id) => {
    setCart((prev) => [...prev, id]);
  }, []);

  return (
    <>
      {products.map((p) => (
        <ProductItem key={p.id} product={p} onAddToCart={handleAdd} />
      ))}
    </>
  );
}

이 구조에서는 ProductList가 리렌더링되더라도 handleAdd 함수는 고정되어 있어서, React.memo로 감싼 ProductItem 컴포넌트가 불필요하게 다시 렌더링되지 않아요!


⚠️ 주의할 점

  • useCallback 남용 금지! 너무 자주 쓰면 코드 가독성 떨어지고, 실제 성능 향상도 없음
  • 정말 리렌더링 병목 구간에서만 쓰는 것이 좋음
  • React.memo는 props 변경 여부만 체크한다는 점 잊지 말기!

🧠 최적화 흐름 요약

  1. 성능 병목 구간 발견 (React DevTools로 확인)
  2. 자식 컴포넌트에 React.memo 적용
  3. props로 전달하는 함수 → useCallback으로 고정
  4. 객체/배열 props → useMemo로 고정

 


📚 추천 공식 리소스


✅ 마무리

React.memouseCallback을 함께 사용하면 불필요한 컴포넌트 리렌더링을 방지할 수 있습니다.

하지만 무조건 적용한다고 좋은 건 아니고, 정말 렌더링 비용이 클 때만 전략적으로 사용하는 것이 핵심이에요.

필요한 곳에만 적절히 사용하는 것이
"지능적인 최적화 전략"입니다 😎

다음 글에서는 React DevTools로 리렌더링 감지하고 최적화 지점 찾는 법에 대해 알아볼게요!

반응형