React.memo`, `useMemo`, `useCallback` 남발로 인한 성능 이슈 해결

송연지·2025년 2월 6일
0

트러블슈팅

목록 보기
17/32

문제상황

최근 마이페이지에서 리스트 및 카드 컴포넌트의 리렌더링을 최적화하기 위해

React.memo, useMemo, useCallback을 적극 활용하여 최적화를 진행했다.
하지만 적용 후 코드가 더 복잡해지고,

⚠️ 정말 필요한 곳에만 최적화를 적용한 것이 맞는지?

⚠️ 오히려 과한 최적화로 인해 성능이 저하될 수도 있는지?

라는 고민이 생겼다. 이에 대해 트러블슈팅을 진행하고, 어떤 기준으로 적용해야 하는지 정리해봤다.

앞서, 메모이제이션이란?
메모이제이션(memoization)은 이전에 계산한 값을 저장함으로써 동일한 계산을 반복하지 않도록 하는 기술입니다. 메모이제이션을 사용하면 이전에 계산한 값을 재사용하여 성능을 향상시킬 수 있습니다.


1.useCallback 남발 문제: 이벤트 핸들러 최적화가 정말 필요할까?

문제 상황

  • useCallback을 사용하면 이벤트 핸들러가 리렌더링될 때마다 새로 생성되지 않도록 최적화할 수 있다.
  • 따라서, 모든 이벤트 핸들러에 useCallback을 적용했다.
tsx
복사편집
const handleClick = useCallback(() => {
  console.log("버튼 클릭!");
}, []);

하지만, 이렇게 모든 핸들러에 useCallback을 적용하는 것이 오히려 성능을 저하시킬 수 있음을 알게 됐다.


원인 분석: useCallback을 남발하면 안 되는 이유

  • useCallback이 오히려 메모리를 추가적으로 사용하며, 함수를 캐싱하는 과정에서 불필요한 오버헤드가 발생할 수 있다.
  • 특히, 단순한 setState 변경 함수useCallback이 없어도 무방하다.

❌ 불필요한 useCallback 적용 예제

const handleToggle = useCallback(() => {
  setIsOpen(prev => !prev);
}, []);

위 코드는 useCallback 없이도 성능 저하 없이 동작 가능

const handleToggle = () => {
  setIsOpen(prev => !prev);
};

해결 방법: useCallback을 언제 써야 할까?

부모 컴포넌트가 자주 리렌더링되며, 자식 컴포넌트로 함수를 props로 넘길 때만 useCallback 사용

이벤트 핸들러가 한 번만 실행되거나, 자식에게 전달되지 않는다면 useCallback을 사용하지 않기

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

📌이렇게 하면 불필요한 메모리 사용을 줄이고 코드도 간결해진다.


2. useMemo를 모든 데이터에 적용하는 문제: 정말 최적화가 되는가?

문제 상황

  • 리스트 데이터를 useMemo로 감싸서 불필요한 렌더링을 줄이고자 했다.
  • 하지만 가공이 필요 없는 데이터까지 useMemo를 적용하는 것이 적절한지 확신이 없었다.
const memoizedList = useMemo(() => guestbooksData.content, [guestbooksData]);

📌 문제점:

  • guestbooksData.content 자체가 이미 서버에서 받아온 값이므로 불변 객체이다.

  • 즉, useMemo를 사용하지 않아도 불필요한 재계산이 발생하지 않는다.

  • 불필요한 useMemo 사용은 오히려 메모리 사용량을 증가시킬 수 있다.


해결 방법: useMemo를 언제 써야 할까?

리스트 데이터가 filter, map, sort 등 연산을 수행할 경우만 useMemo를 사용

가공이 필요 없는 원본 데이터에는 useMemo를 사용하지 않기

❌ 불필요한 useMemo 적용 예제

tsx
복사편집
const memoizedList = useMemo(() => guestbooksData.content, [guestbooksData]);

최적화된 코드

const guestbookList = guestbooksData.content; // 그대로 사용해도 무방

📌 불필요한 메모리 사용을 줄이고, 코드 가독성도 향상됨


3. React.memo 적용 범위 문제: 작은 컴포넌트에도 적용해야 할까?

문제 상황

  • React.memo를 적용하여 리스트 아이템(GatheringItem, MainCard)의 불필요한 리렌더링을 방지했다.
  • 하지만 작은 컴포넌트에도 React.memo를 적용하는 것이 과연 적절한가?

원인 분석

  • React.memo를 적용하면, 컴포넌트의 props가 변경되지 않으면 리렌더링을 방지할 수 있다.
  • 하지만 작은 UI 요소(단순한 텍스트 출력, 스타일링 요소 등)는 원래 렌더링 비용이 낮다.
  • props가 자주 변경되는 경우, React.memo를 적용해도 리렌더링이 계속 발생하므로 의미가 없다.

❌ 불필요한 React.memo 적용 예제

const SmallComponent = React.memo(({ text }) => {
  return <p>{text}</p>;
});

최적화된 코드

const SmallComponent = ({ text }) => {
  return <p>{text}</p>;
};

📌 작은 컴포넌트에는 React.memo를 적용하지 않는 것이 더 효율적! 그치만 대부분은 작은경우에도 많이 쓴다. 이는 브라우저 성능차이가 있음을 확인하고 느려졌으면 사용해보자~!


🚀 최종 정리: React.memo, useMemo, useCallback은 언제 써야 할까?

언제 사용해야 하나?언제 사용하지 말아야 하나?
useCallback부모에서 자식으로 props로 전달될 때 (이벤트 핸들러)setState만 변경하는 단순한 함수
useMemo비싼 연산(필터링, 정렬 등)을 캐싱할 때단순한 값(숫자, 문자열)에는 필요 없음
React.memo부모가 자주 리렌더링되지만, 자식은 변경되지 않는 경우작은 UI 컴포넌트 또는 props가 자주 변경되는 경우

CSR 환경에서 React.memo, useMemo, useCallback을 어떻게 활용해야 할까?

  • 리스트 렌더링 최적화 → useMemo를 통해 가공된 데이터 캐싱
  • 이벤트 핸들러 최적화 → 부모가 리렌더링될 때 자식에게 props로 전달되는 함수만 useCallback 사용
  • 컴포넌트 최적화 → 부모 리렌더링 빈도가 높을 때 React.memo 적용

🔥 결론: "최적화도 필요할 때만 하자!"

단순한 setState 변경 함수에는 useCallback 사용하지 말 것

가공이 필요 없는 데이터에는 useMemo 사용하지 말 것

작은 UI 컴포넌트에는 React.memo를 적용하지 말 것

렌더링 비용이 높은 경우(리스트, 차트, 복잡한 UI)에만 최적화 적용

🎯 즉, React.memo, useMemo, useCallback은 남발하지 말고 "필요할 때만" 사용해야 최적화 효과를 제대로 볼 수 있다!

profile
프론트엔드 개발쟈!!

0개의 댓글