개발/React

[React] 성능 잡는 Hook! useMemo, useCallback, 그리고 React.memo 정리 끝판왕

pangil_kim 2025. 7. 1. 21:11
728x90
React.memo

: 컴포넌트 자체를 메모이제이션하여 props가 바뀌지 않으면 다시 렌더링하지 않도록 한다.

 

1) 사용하는 경우

  • 부모 컴포넌트가 자주 렌더링되는데, 자식 컴포넌트의 props가 변하지 않는 경우
  • 불필요한 리렌더링 방지 목적

2) 예시

: React.memo를 사용해 부모 컴포넌트가 리렌더링되더라도 자식 컴포넌트의 props가 변하지 않으면 자식 컴포넌트의 렌더링을 생략하여 성능을 최적화한 예제

// Child.tsx
import React from 'react';

const Child = ({ value }: { value: number }) => {
  console.log('🔄 자식 컴포넌트 렌더링');
  return <div>값: {value}</div>;
};

// memo 적용이며, 이렇게 하면 다른 파일에서 import할 때 자동으로 memo된 최적화 버전이 import됨
export default React.memo(Child);
// Parent.tsx
import React, { useState } from 'react';
import Child from './Child';

export default function Parent() {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState(100);

  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>카운트 증가</button>
      <Child value={value} />
    </>
  );
}

 

(1) 최적화 포인트

  • count가 바뀔 때마다 부모가 리렌더링되지만,
  • value는 변하지 않기 때문에 Child는 React.memo로 렌더링을 막을 수 있다.

 

3) 결론

: 부모는 변했지만 자식한테 전달된 값은 그대로니까, 굳이 다시 그릴 필요가 없다
→ 그래서 React가 렌더링을 "스킵"해주는 거예요.

 

 

 

useMemo

: 값(value)을 메모이제이션한다. 즉, 특정 연산의 결과를 캐싱해서 의존값이 바뀌지 않으면 연산을 다시 하지 않는다. 

 

 

0) 사용 방법

const memoizedValue = useMemo(() => 계산식, [의존성배열]);
  • 계산식: 복잡하거나 비용이 많이 드는 연산
  • 의존성 배열: 값이 바뀌면 다시 계산 / 안 바뀌면 이전 결과 재사용

1) 사용하는 경우

  • 렌더링 시 비용이 큰 연산(heavy computation)이 수행될 경우
  • 연산 결과가 동일한데도 매번 계산하는 불필요한 연산을 방지하고 싶을 때
  • 복잡한 계산이 많은 컴포넌트에서 불필요한 성능 낭비 방지

2) 예시

: useMemo를 사용해 input값이 바뀔 때만 무거운 계산을 실행하고, 그렇지 않으면 이전 계산 결과를 재사용하여 렌더링 성능을 최적화한 예제

// Calculator.js
import React, { useState, useMemo } from 'react';

function heavyCalculation(num) {
  console.log('💥 계산 중...');
  let result = 0;
  for (let i = 0; i < 100000000; i++) {
    result += i % num;
  }
  return result;
}

export default function Calculator() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState(5);

  // input이 바뀔 때만 계산이 다시 일어남
  const calculated = useMemo(() => heavyCalculation(input), [input]);

  return (
    <>
      <input
        type="number"
        value={input}
        onChange={(e) => setInput(Number(e.target.value))}
      />
      <p>계산 결과: {calculated}</p>
      <button onClick={() => setCount((c) => c + 1)}>카운트 증가</button>
    </>
  );
}

 

 

(1) 최적화 포인트

  • count는 계산과는 무관한 상태이지만, 값이 바뀌면 Calculator 컴포넌트가 리렌더링됨
  • useMemo를 쓰지 않으면 heavyCalculation(input)이 매번 다시 실행됨 → 비효율 발생
  • useMemo를 쓰면 input이 바뀌지 않으면 이전 계산 결과를 재사용

3) 결론

: 계산 결과가 이전과 같다면 굳이 다시 계산할 필요가 없다
→ 그래서 useMemo가 계산 결과를 기억하고 있다가 그대로 써주는 것이다.
→ 불필요한 계산 로드를 줄이고, 렌더링 시 퍼포먼스를 지키는 전략이다.

 

 

 

useCallback

: 함수(function)를 메모이제이션한다. 매번 새로운 함수 객체가 생성되는 걸 막고, 같은 참조를 유지함.

 

0) 사용 방법

const memoizedCallback = useCallback(() => {
  // 실행할 함수 내용
}, [의존성배열]);

 

1) 사용하는 경우

  • 자식 컴포넌트에 함수를 props로 전달할 때
  • 부모 컴포넌트가 리렌더링되면서 함수 객체가 매번 새로 만들어지는 현상을 막고 싶을 때
  • React.memo와 함께 사용하면 props 비교 시 참조값이 유지되기 때문에 리렌더링 방지에 효과적

2) 예시

: useCallback을 사용하여 함수의 참조값을 고정함으로써, 부모 컴포넌트가 리렌더링되어도React.memo로 감싼 자식 컴포넌트의 불필요한 렌더링을 방지하는 예시

// Child.js
import React from 'react';

function Child({ onClick }) {
  console.log('🧒 자식 렌더링');
  return <button onClick={onClick}>자식 버튼</button>;
}

export default React.memo(Child); // memo 적용
// Parent.js
import React, { useState, useCallback } from 'react';
import Child from './Child';

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

  // 매 렌더링마다 새로 만들어지는 함수 (비효율)
  // const handleClick = () => console.log('클릭됨');

  // useCallback 사용 → 참조 유지됨
  const handleClick = useCallback(() => {
    console.log('클릭됨');
  }, []);

  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>카운트 증가</button>
      <Child onClick={handleClick} />
    </>
  );
}

 

 

(1) 최적화 포인트

  • Child 컴포넌트는 React.memo로 감싸져 있지만
  • 부모가 리렌더링될 때마다 handleClick 함수가 새로운 객체로 생성되면, 자식도 리렌더링된다.
  • useCallback을 사용하면 함수의 참조값이 유지되어 Child는 렌더링을 건너뛴다.

 

3) 결론

: 함수 로직은 똑같은데, 매번 새로운 객체로 만들어지면 자식 컴포넌트 입장에서는 “다른 함수”로 인식한다.
→ 그래서 useCallback을 쓰면 같은 참조를 유지해서
React.memo와 함께 진짜로 바뀐 props만 감지하도록 도와주는 것이다.

 

728x90