frontend/react

[React] React.js 강좌 11. 리렌더링 최적화 – React.memo, useMemo, useCallback 완벽 정리

mirabo01 2025. 11. 10. 08:51

1. 리액트의 렌더링 원리 간단 복습

리액트는 상태(state) 혹은 props가 변경되면 해당 컴포넌트와 자식 컴포넌트를 다시 렌더링합니다.
이건 기본적으로 좋은 구조지만, 불필요한 렌더링이 많아지면 성능 저하로 이어집니다.

예를 들어, 아래 코드를 살펴봅시다.

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

  return (
    <div>
      <p>클릭 횟수: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <Child />
    </div>
  );
}

function Child() {
  console.log('Child 렌더링됨');
  return <div>자식 컴포넌트</div>;
}

버튼을 클릭할 때마다 Parent의 상태가 바뀌기 때문에,
Child도 불필요하게 계속 렌더링됩니다.

이 문제를 해결하기 위한 대표적인 세 가지 도구가
React.memo, useMemo, useCallback입니다.


2. React.memo – 컴포넌트 자체를 메모이제이션

React.memo는 컴포넌트 자체를 메모이제이션(memoization) 합니다.
즉, props가 바뀌지 않으면 해당 컴포넌트를 다시 렌더링하지 않습니다.

const Child = React.memo(function Child({ name }) {
  console.log('Child 렌더링됨');
  return <div>안녕하세요, {name}</div>;
});

그리고 부모 컴포넌트는 이렇게 작성합니다.

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

  return (
    <div>
      <p>클릭 횟수: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <Child name="홍길동" />
    </div>
  );
}

이제 버튼을 눌러도 Child는 더 이상 렌더링되지 않습니다.
왜냐하면 name props가 바뀌지 않기 때문입니다.

즉, React.memo는

“props가 변하지 않으면 컴포넌트를 다시 그리지 않는다”
라는 단순하지만 강력한 규칙을 따릅니다.


3. React.memo 내부 비교 원리

React.memo는 기본적으로 **얕은 비교(shallow comparison)**를 수행합니다.
즉, props가 객체나 배열이면 내부 값이 같더라도 새로운 참조(reference)일 경우 렌더링이 발생합니다.

const Child = React.memo(function Child({ info }) {
  console.log('Child 렌더링');
  return <div>{info.name}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const info = { name: '홍길동' };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <Child info={info} />
    </div>
  );
}

이 경우 info는 매 렌더링마다 새 객체가 생성되므로
Child는 계속 렌더링됩니다.

이럴 땐 useMemo를 함께 사용해야 합니다.


4. useMemo – 계산된 값을 메모이제이션

useMemo는 **값(value)**을 메모이제이션합니다.
즉, 특정 연산 결과를 캐싱해 불필요한 재계산을 막습니다.

const info = useMemo(() => ({ name: '홍길동' }), []);

이제 이 info 객체는 첫 렌더링 이후로 재생성되지 않습니다.
그래서 React.memo로 감싼 자식 컴포넌트도 다시 렌더링되지 않습니다.


5. useMemo의 실제 활용 예시

복잡한 계산이 들어간 로직에 자주 사용됩니다.

function ExpensiveCalculation({ num }) {
  const result = useMemo(() => {
    console.log('복잡한 연산 실행 중...');
    let total = 0;
    for (let i = 0; i < 1e8; i++) total += num * 2;
    return total;
  }, [num]);

  return <p>결과: {result}</p>;
}

num이 바뀔 때만 계산을 다시 수행하고,
그 외에는 기존 값을 재활용합니다.

즉, useMemo는 CPU 연산을 절약하는 훅입니다.


6. useCallback – 함수를 메모이제이션

useCallback은 함수를 메모이제이션합니다.
리액트는 함수형 컴포넌트가 렌더링될 때마다 내부 함수를 새로 생성하기 때문에,
자식 컴포넌트에 함수를 props로 넘길 경우 리렌더링이 발생할 수 있습니다.

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

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

  const handleClick = () => console.log('clicked');

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <Child onClick={handleClick} />
    </div>
  );
}

이 코드는 count가 바뀔 때마다 handleClick이 새로 만들어지므로,
Child가 매번 리렌더링됩니다.

이를 방지하려면 useCallback을 사용해야 합니다.

const handleClick = useCallback(() => console.log('clicked'), []);

이제 함수가 메모이제이션되어,
의존성 배열이 바뀌지 않는 한 동일한 참조를 유지합니다.


7. useMemo vs useCallback 비교

구분 useMemo useCallback

목적 계산된 값을 캐싱 함수를 캐싱
반환값 메모이제이션된 값 메모이제이션된 함수
사용 예시 복잡한 계산, 객체 캐싱 이벤트 핸들러, 콜백 전달
공통점 의존성 배열이 바뀌지 않으면 재생성되지 않음  

예를 들어, 이런 식으로 함께 사용할 수도 있습니다.

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

const user = useMemo(() => ({ name: '홍길동' }), []);

이렇게 하면 객체도, 함수도 새로 생성되지 않기 때문에
자식 컴포넌트가 불필요하게 다시 렌더링되는 것을 방지할 수 있습니다.


8. 불필요한 최적화의 위험

메모이제이션을 무조건 사용하는 것은 좋지 않습니다.
모든 useMemo, useCallback은 오히려 메모리 오버헤드를 유발할 수 있습니다.

측정 가능한 성능 저하가 있을 때만 최적화를 적용하라.

즉, 실제 렌더링 지연이나 연산 부담이 크지 않다면,
불필요한 최적화는 코드 가독성만 떨어뜨릴 수 있습니다.


9. 마무리

이번 강의에서는 리액트 렌더링 최적화의 핵심 개념인
React.memo, useMemo, useCallback을 다뤘습니다.

핵심 정리:

  • React.memo : 컴포넌트를 메모이제이션
  • useMemo : 계산된 값을 메모이제이션
  • useCallback : 함수를 메모이제이션
  • 불필요한 최적화는 피하고, 실제 성능 이슈가 있는 곳에만 적용

다음 강의에서는 **리액트 라우터(React Router)**를 이용해
페이지 이동과 동적 라우팅을 구현하는 방법을 단계별로 배워보겠습니다.