우당탕탕

React useMemo, useCallback 진짜 필요할 때만 쓴 경험담 본문

언어/JavaScript

React useMemo, useCallback 진짜 필요할 때만 쓴 경험담

모찌모찝 2026. 5. 31. 19:49

React 프로젝트 하면서 useMemouseCallback 훅을 쓴다고 성능이 무조건 좋아지는 줄 알았어요. 저도 처음엔 무작정 다 곳곳에 남발하다가, 오히려 코드가 꼬이고 디버깅하기 어려워진 경험이 있거든요. 이걸 구현하느라 진짜 삽질을 몇 번이나 했습니다.

이번 글에선 제가 겪은 실패 사례 중심으로, useMemo, useCallback을 언제 써야 효과적이고 과용이 왜 문제인지를 생생하게 풀어 볼게요. 그리고 제가 직접 써보고 효과 있던 적용 기준과 코드 예시도 자세히 담았습니다.

개발 환경 / 버전 정보

이 글에서 쓴 예제는 React 18.2.0 기준이고, 개발 환경은 TypeScript 4.9Vite 4.3를 사용했습니다. 물론 JS 환경도 완벽히 동일하게 적용 가능해요.

제가 겪은 대표적 실패 사례들

사실 이 부분이 제일 중요한데, 저도 한창 React 배우던 시절에 useMemo와 useCallback을 무조건 써야만 성능이 좋아진다고만 생각했거든요. 근데 이렇게 무작정 쓰면 다음과 같은 문제들이 생기더라고요.

  • 불필요한 메모리 사용 증가: 기억해야 하는 값과 함수가 많아지면서 오히려 메모리 부담이 커졌어요.
  • 오히려 리렌더링이 많아짐: 의존성 배열 관리를 잘못해 무한 루프가 생기고, 이로 인해 렌더링이 계속 반복된 적도 있었습니다.
  • 디버깅 난항: 렌더링이 예상과 다르게 돼서 원인을 찾는 데 몇 시간을 허비했죠.
  • 코드 가독성 저하: 너무 남발해서 코드가 더 복잡해졌어요. 나중에 저도 제 코드를 다시 보면 무슨 의도로 쓴 건지 헷갈릴 정도였어요.

특히 저는 이런 상황 때문에 프로젝트를 2번 이상 다시 리팩터링하게 됐는데, 그때마다 배운 점들이 정말 많았습니다.

useMemo와 useCallback 진짜 써야 할 때는?

많이들 헷갈려하시는 게 "언제 써야 하는지 정확한 기준"인데, 저도 처음엔 딱 감이 안 왔거든요. 근데 경험해보니 중요한 건 계산 비용이 꽤 크거나, 자식 컴포넌트에 넘기는 함수가 매번 새로 만들어지는 게 문제일 때만 써야 한다는 겁니다.

  • useMemo: 무거운 계산 결과를 저장할 때만 써요. 그런데 계산이 ‘가벼운 단순 값’이면 굳이 기억할 필요 없더라고요.
  • useCallback: 자식에게 props로 넘기는 함수가 자주 변하면 불필요한 리렌더링이 생겨서, 그걸 막기 위해 씁니다.

예를 들어서, 자식 컴포넌트가 React.memo로 감싸져 있는데 부모에서 넘기는 함수가 매번 새로 만들어지면 자식이 다시 렌더링될 수밖에 없거든요. 그때 useCallback이 진짜 효과적입니다.

제가 쓴 간단 예제와 설명

먼저 무거운 계산이 있을 때 useMemo 쓰는 예제부터 볼게요.

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

// 무거운 계산 함수 (예: 피보나치수 계산)
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

function FibonacciComponent() {
  const [count, setCount] = useState(35);
  const [increment, setIncrement] = useState(0);

  // useMemo를 써서 무거운 계산 결과를 메모이제이션
  const fibValue = useMemo(() => fibonacci(count), [count]);

  return (

피보나치({count}) = {fibValue}

다른 상태: {increment}


  );
}

export default FibonacciComponent;

이 코드는 count가 바뀔 때만 피보나치 계산이 다시 실행되고, 다른 상태인 increment가 바뀌어도 재계산하지 않아요. 이게 useMemo의 핵심이죠.

다음은 자식 컴포넌트에 넘길 함수를 useCallback으로 감싸서 불필요한 렌더링을 막는 예시입니다.

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

const Child = React.memo(({ onClick }) => {
  console.log('Child 렌더링!');
  return 클릭;
});

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

  // useCallback으로 함수 메모이제이션
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 의존성 없으니 한 번만 생성

  return (

count: {count}


  );
}

export default Parent;

이렇게 하면 Child 컴포넌트는 handleClick이 바뀌지 않아서 불필요하게 재렌더링되지 않고 성능이 좋아집니다. 저도 이 부분 때문에 한참 해맸었는데 정말 효과적이에요.

제가 겪은 삽질과 에러 사례들

사실 처음엔 useCallback 안에 의존성 배열을 잘못 넣어서 무한루프에 빠진 적 있어요. 특히 의존성 배열에 상태나 props를 넣을지 말지 헷갈려서 수십 번 다시 코딩했죠.

const handleClick = useCallback(() => {
  doSomething();
}, [someState]); // someState가 자주 바뀌면 함수도 매번 새로 생성돼서 무한루프 위험!

이 에러가 왜 나는지 한참 헤맸는데, 결국 의존성 배열을 최소화해야 한다는 교훈을 얻었어요. 그리고 React ESLint 플러그인 도움으로 이 부분을 많이 잡아줬습니다.

또한 무조건 useMemo로 감싸 버린 값이 의존성 배열 관리를 빼먹어, 최신 상태가 아닌 예전 값을 참조하는 버그도 있었습니다. 이걸로 디버깅한 시간이 정말 아깝더라고요.

조금 더 알면 좋은 팁들

사실 useMemo/useCallback을 아무리 잘 써도 React.memo와 함께 쓰지 않으면 큰 성능 향상을 체감하기 어려울 때가 많아요. 저는 이 조합을 꼭 같이 쓰려고 합니다.

그리고, 작은 컴포넌트나 가벼운 계산엔 절대 쓰지 말라고 권하고 싶어요. 너무 일찍 최적화하는 것도 오히려 독이거든요.

마지막으로, 성능 최적화는 개발자가 직접 프로파일러를 돌려보면서 병목 구간을 찾는 게 가장 정확합니다. 무조건 남발하지 말고, 어떤 부분이 느려지는지 구체적으로 파악하는 습관이 중요해요.

자주 물어보는 질문들

Q. useMemo와 useCallback 중 뭘 먼저 써야 하나요?

A. 먼저 최적화하려는 대상이 값인지 함수인지에 따라 달라요. 무거운 값 계산이면 useMemo, 자식에게 넘길 함수면 useCallback부터 적용하세요.

Q. 의존성 배열에 상태를 넣어야 할지 말아야 할지 헷갈립니다.

A. 일반적으로는 바뀌는 값은 모두 포함해야 하지만, 너무 자주 바뀌어 무한루프 위험이 있는 경우에는 설계 자체를 다시 고민해 봐야 해요. ESLint React Hooks 규칙을 잘 참고하세요.

Q. useMemo/useCallback 없이 문제되는 상황은 언제인가요?

A. 복잡한 컴포넌트가 자주 리렌더링될 때, 특히 자식 컴포넌트는 React.memo로 래핑해 놨는데 props가 바뀌어 계속 다시 렌더링된다면 고민해볼 타이밍입니다.

저도 초반에 이런 부분 때문에 삽질 많이 했지만, 지금은 이 경험 덕분에 React 성능 최적화를 훨씬 명확하게 할 수 있게 됐어요. 앞으로도 계속 프로파일링과 실제 성능 테스트를 병행하면서 쓸 것 같습니다.

Comments