대용량 트래픽 성능 최적화(2)-메모이제이션(React.memo, useMemo, useCallback)

hannah·2026년 2월 12일

TroubleShooting

목록 보기
7/9
post-thumbnail

지난 글에서는 Zustand의 useShallow를 활용하여 전역 상태 구독 시 발생하는 불필요한 리렌더링을 일차적으로 제어해 보았다. 하지만 스토어 레벨의 최적화를 마친 후에도 1,000개 이상의 카드가 렌더링된 환경에서 DnD 기능을 사용할 때 스크롤이 끊기는 렌더링 지연 현상은 완벽히 해결되지 않았다.

여전히 카드 하나를 조작할 때 불필요하게 렌더링되는 컴포넌트들이 존재했기 때문이다. 의도적으로 발생시킨 이 부하를 매끄럽게 해결하기 위해, 이번에는 React가 제공하는 컴포넌트 레벨의 메모이제이션 기법을 적용하기로 했다.


1. React.memo를 활용한 불필요한 컴포넌트 리렌더링 방지

가장 일차적으로 확인한 부분은 컴포넌트의 잦은 리렌더링이었다. React의 특성상 부모 컴포넌트인 보드가 렌더링되면 자식 컴포넌트인 카드들도 모두 다시 렌더링된다. 변경된 상태와 무관한 컴포넌트의 리렌더링을 줄이고자 React.memo를 사용했고 props가 변경되지 않은 컴포넌트는 리렌더링을 건너뛰도록 처리했다.

// Card.tsx
export const Card = memo(CardInner);

// SortableCard.tsx  
export const SortableCard = memo(SortableCardInner);

React Developer Tools 익스텐션을 통해 확인해보니 이 조치만으로도 렌더링 범위가 확 줄어드는 것을 확인할 수 있었다. 한 컬럼의 카드 배열이 변경되어도 다른 컬럼에 있는 카드 컴포넌트들은 기존 렌더링 결과를 재사용하며 불필요한 리렌더링을 수행하지 않게 되었다.

2. useCallback을 이용한 이벤트 핸들러 함수 참조 안정화

렌더링 내역을 추적해 보니 React.memo를 적용했는데도 객체 타입인 '함수의 참조값'이 여전히 불필요하게 렌더링이 발생하고 있었다.

부모 컴포넌트가 렌더링될 때마다 카드 조작과 관련된 이벤트 핸들러 함수들이 메모리상에 새롭게 할당되어 자식 컴포넌트의 props로 전달된다. 이로 인해 React.memo는 이전과 다른 새로운 props가 전달되었다고 판단해 메모이제이션을 무효화하고 리렌더링을 발생시킨 것이다.

그래서 함수 참조를 고정하기 위해 useCallback을 적용했다.

// Board.tsx
const handleAddCard = useCallback(
  (columnId: string, title: string) => {
    // 카드 추가 로직
    ...
  },
  [cards, showToast, addOptimisticCards, setCards],
);

Card 컴포넌트에 전달되는 onDelete, onUpdateTitle 등의 함수 참조가 고정되면서 React.memo가 의도한 대로 동작했다.

3. useMemo를 통한 복잡한 연산 결과 캐싱

컴포넌트의 리렌더링을 제어한 후에는 내부 로직의 연산 효율성을 검토했다. 칸반 보드의 특성상 전체 카드 배열에서 각 컬럼에 속하는 카드만 필터링하여 렌더링해야 한다. 카드가 1,000개라면 매 렌더링마다 1,000개의 요소를 순회하며 필터링하는 소모적인 비용이 발생한다.

이와 같은 반복적인 연산을 줄이기 위해 useMemo를 도입하여 필터링된 결과값을 캐싱해 두었다.

// Board.tsx
const columnCardsMap = useMemo(() => {
  const map = new Map<string, Card[]>();
  for (const col of columns) {
    map.set(col.id, optimisticCards.filter(item => item.columnId === col.id));
  }
  return map;
}, [columns, optimisticCards]);

매번 실행되던 필터링 로직이 이제 optimisticCardscolumns 상태가 실제로 변경될 때만 재계산된다. 자바스크립트 스레드가 부담하던 무거운 계산 비용을 덜어내어 전반적인 성능이 향상되었다.


전역 상태 최적화(useShallow)에 이어 이번 메모이제이션 적용까지 거치면서, 불필요한 렌더링과 연산 비용을 눈에 띄게 줄일 수 있었다.

이어지는 다음 포스트에서는 이러한 대용량 데이터를 화면에 더욱 효율적으로 렌더링하기 위해 @tanstack/react-virtual의 가상화 개념을 적용해 본 과정을 작성하려고 한다!

0개의 댓글