일상이 개발

React.memo 완벽 정리: 컴포넌트 리렌더링을 막는 고급 최적화 기법

디어노미 2025. 4. 5. 11:53
반응형

🚀 React.memo 완벽 정리: 컴포넌트 리렌더링을 막는 고급 최적화 기법

React.memo는 React에서 제공하는 고차 컴포넌트(Higher-Order Component)입니다.
컴포넌트의 props가 바뀌지 않으면, 해당 컴포넌트를 리렌더링하지 않도록 만들어줍니다.

즉, 불필요한 렌더링을 방지하여 성능을 최적화할 수 있는 도구예요.
하지만 언제, 왜, 어떻게 써야 할지를 제대로 이해하지 못하면 오히려 디버깅이 어려운 코드가 될 수도 있습니다.

이번 글에서는 React.memo의 개념, 사용법, 작동 원리, 주의점까지 심도 있게 정리해봅니다.


🔍 React.memo란?

React.memo는 함수형 컴포넌트를 메모이제이션(memoization)하여, props가 변경되지 않는 한 컴포넌트를 다시 렌더링하지 않습니다.

const MemoizedComponent = React.memo(OriginalComponent);

이렇게 감싸면, props가 변경되지 않는 한 컴포넌트가 React의 Virtual DOM diff 과정에서 제외됩니다.


✅ 기본 사용 예시

// 자식 컴포넌트
const Child = React.memo(({ name }) => {
  console.log('Child 렌더링');
  return <p>안녕하세요, {name}</p>;
});

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

  return (
    <>
      <Child name="철수" />
      <button onClick={() => setCount(count + 1)}>+1</button>
    </>
  );
}

위 코드에서 Child는 props(name)가 바뀌지 않기 때문에 재렌더링되지 않습니다. 콘솔 로그를 찍어보면 확인할 수 있어요.


🔄 리렌더링 원리 다시 보기

React는 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 기본적으로 함께 리렌더링합니다.
이때 자식 컴포넌트에 전달되는 props가 이전과 동일한 값이라도 함수나 객체이면 새로 생성된 것으로 간주됩니다.

이때 React.memo를 사용하면, 이전 props와 새 props가 같은 경우
자식 컴포넌트의 리렌더링을 막을 수 있습니다.

⚙️ React.memo 내부 작동 방식

React.memoObject.is()를 사용하여 props를 얕은 비교(shallow compare)합니다.

즉, 다음과 같은 경우에만 리렌더링을 막습니다:

  • 기본 타입 값(숫자, 문자열 등)의 값이 이전과 같을 때
  • 객체나 배열, 함수의 참조가 동일할 때

아래는 비교 예시입니다:

const obj1 = { a: 1 };
const obj2 = { a: 1 };
console.log(obj1 === obj2); // false (참조 다름)

그래서 부모에서 함수를 매번 새로 선언하거나, 객체를 만들어서 넘기면 React.memo가 무용지물이 될 수 있어요.


🔧 해결 방법: useCallback & useMemo와 함께 사용

함수 props → useCallback

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

객체 props → useMemo

const style = useMemo(() => ({ color: "blue" }), []);

→ 이렇게 하면 props의 참조값이 고정되어 React.memo의 효과가 극대화됩니다.


📌 커스텀 비교 함수 사용 (areEqual)

React.memo는 기본적으로 얕은 비교를 하지만, 두 번째 인자로 비교 함수를 전달할 수도 있어요.

const Memoized = React.memo(MyComponent, (prevProps, nextProps) => {
  return prevProps.value === nextProps.value;
});

→ props 구조가 복잡하거나, 특정 값만 비교하고 싶을 때 유용합니다.


📊 실전에서 자주 쓰는 패턴

  • 리스트 컴포넌트 렌더링 최적화 (예: 채팅 메시지 리스트)
  • Props가 자주 바뀌지 않는 컴포넌트
  • 함수형 컴포넌트에서 useCallback과 함께 자식 컴포넌트를 감쌀 때
  • 다수의 고정 UI 요소 렌더링 (아이콘, 버튼 등)

🧪 언제 React.memo를 쓰지 말아야 할까?

React.memo는 props 비교에 따른 비용이 발생합니다. 때로는 이 비교 과정이 컴포넌트를 리렌더링하는 것보다 오히려 비효율적일 수도 있어요.

아래와 같은 경우에는 굳이 사용하지 않아도 됩니다:

  • 컴포넌트가 매우 가볍고, 렌더링 비용이 낮은 경우
  • props가 자주 바뀌는 경우
  • 모든 props가 새로운 참조(함수/객체)인 경우

⚠️ 실수하기 쉬운 사례

1. 객체나 함수 props를 매 렌더링마다 새로 생성

// ❌ React.memo 효과 없음
<Child obj={{ a: 1 }} />

→ 매 렌더링마다 새로운 참조가 생성되므로 React.memo는 무용지물

2. useCallback 없이 함수 props 전달

// ❌ 새로운 함수 참조 생성됨
<Child onClick={() => console.log('클릭')} />

→ 자식 컴포넌트가 매번 리렌더링됨

✅ useCallback 적용

const handleClick = useCallback(() => console.log('클릭'), []);
<Child onClick={handleClick} />

📚 추천 공식 리소스


✅ 마무리 정리

  • React.memo는 props가 바뀌지 않으면 컴포넌트 리렌더링을 방지합니다.
  • 얕은 비교(Object.is)만 수행하기 때문에 참조형 props는 주의가 필요합니다.
  • useCallback / useMemo를 함께 쓰면 최적화 효과가 커집니다.
  • 무조건 사용하기보단, 성능 병목 구간에만 전략적으로 사용하는 것이 베스트!
React.memo는 성능을 향상시키는 칼이 될 수도 있고,
쓸데없이 코드만 복잡하게 만드는 짐이 될 수도 있습니다.
무조건 적용이 아니라, 필요한 곳에 똑똑하게 적용해보세요!

다음 글에서는 React.memo + useCallback을 조합한 고급 컴포넌트 최적화 전략을 다뤄볼 예정입니다 😎

반응형