개발/React

[고급반] Step 1-2. Memoization 전략: memo, useMemo, useCallback의 명과 암

ophelisis 2025. 12. 23. 13:52
반응형

리액트 성능 최적화의 꽃이자, 동시에 가장 오용되기 쉬운 기능이 바로 **메모이제이션(Memoization)**입니다. "일단 다 감싸고 보자"는 식의 접근은 오히려 애플리케이션의 성능을 떨어뜨릴 수 있습니다.

이번 섹션에서는 메모이제이션의 정확한 동작 원리와 언제 사용하고, 언제 멈춰야 하는지에 대한 명확한 기준을 세워보겠습니다.


1. 왜 메모이제이션이 필요한가? (참조 동일성)

리액트에서 컴포넌트는 자신의 state가 바뀌거나, 부모 컴포넌트가 재렌더링될 때 함께 재렌더링됩니다. 이때 자바스크립트의 참조 동일성(Referential Equality) 문제가 발생합니다.

  • 문제: 컴포넌트가 재렌더링될 때마다 내부에서 선언된 객체, 배열, 함수는 새로운 메모리 주소를 가집니다.
  • 결과: 리액트 입장에서는 내용은 같아도 "새로운 값"이 들어온 것으로 간주하여 자식 컴포넌트까지 불필요하게 다시 그리게 됩니다.

2. 메모이제이션의 3총사

2-1. React.memo: 컴포넌트 자체를 기억하기

부모 컴포넌트가 바뀌어도 전달받는 props가 변하지 않았다면 재렌더링을 건너뛰게 합니다.

  • 사용 시점: 컴포넌트가 같은 props로 자주 렌더링되거나, 렌더링 비용이 큰 UI일 때.
  • 주의: Props로 객체나 함수를 넘길 경우, 뒤에 나올 useMemo나 useCallback 없이 React.memo만 쓰면 효과가 없습니다. (참조값이 매번 바뀌기 때문)

2-2. useMemo: 값(Value)을 기억하기

비싼 계산(예: 대용량 데이터 필터링, 정렬)의 결과값을 메모리에 저장합니다.

JavaScript
 
const sortedList = useMemo(() => {
  return heavySort(data); // 데이터가 바뀔 때만 실행됨
}, [data]);

2-3. useCallback: 함수(Function)를 기억하기

함수 인스턴스 자체를 기억합니다. 주로 React.memo를 사용 중인 자식 컴포넌트에 함수를 props로 전달할 때 필수적으로 사용됩니다.

[Image showing the relationship between useCallback in parent and React.memo in child]


3. ⚠️ 메모이제이션의 "암(Dark Side)": 무분별한 사용의 위험성

많은 개발자가 놓치는 사실은 **"메모이제이션도 비용이다"**라는 점입니다.

  1. 메모리 비용: 이전 값을 메모리에 들고 있어야 하므로 메모리 점유율이 높아집니다.
  2. 비교 비용: 재렌더링 시마다 의존성 배열(dependency array) 내의 값들을 하나하나 비교하는 오버헤드가 발생합니다.
  3. 가독성 저해: 코드가 장황해지고 유지보수가 어려워집니다.

현명한 판단 기준:

  • 단순히 텍스트를 출력하는 작은 컴포넌트에는 React.memo를 쓰지 마세요.
  • 간단한 계산(예: a + b)에는 useMemo를 쓰지 마세요.
  • 먼저 코드를 최적화하고, 그래도 성능 문제가 있을 때 측정(Profiling) 후 적용하세요.

4. 실전 최적화 전략 (Best Practice)

상황 추천 전략
단순 UI 컴포넌트 최적화 불필요
차트, 리스트 등 무거운 컴포넌트 React.memo 적용
부모에서 자식으로 함수 전달 시 useCallback + 자식 React.memo 조합
복잡한 데이터 가공 로직 useMemo 적용
반응형