모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

18강 / 전체 30강

useReducer

5분 읽기 조회 5

useMemo와 useCallback으로 불필요한 재계산·재생성을 방지하고, React.memo로 컴포넌트 리렌더링을 최적화하는 방법과 올바른 사용 기준을 배웁니다.

⚡ 성능 최적화는 꼭 필요할 때만

중요한 경고부터: useMemo, useCallback, React.memo는 남용하면 오히려 코드가 복잡해지고 성능이 나빠질 수 있습니다. 리액트는 기본적으로 충분히 빠릅니다. 이 훅들은 측정된 성능 문제가 있을 때만 적용하세요. 먼저 동작하게 만들고, 그다음 측정하고, 느리면 최적화합니다.

📦 useMemo — 비싼 계산 결과 캐싱

useMemo는 연산 결과를 메모이제이션(memoization)합니다. 의존성이 바뀌지 않으면 이전 계산 결과를 재사용합니다.

function ProductTable({ products, sortBy, filterText }: {
  products: Product[]
  sortBy: 'name' | 'price'
  filterText: string
}) {
  // filterText나 sortBy가 바뀔 때만 재계산
  const processedProducts = useMemo(() => {
    console.log('무거운 계산 실행됨')  // 이 로그가 매 렌더마다 찍히면 문제
    return products
      .filter((p) => p.name.includes(filterText))
      .sort((a, b) =>
        sortBy === 'price' ? a.price - b.price : a.name.localeCompare(b.name)
      )
  }, [products, sortBy, filterText])

  return (
    <table>
      <tbody>
        {processedProducts.map((p) => (
          <tr key={p.id}>
            <td>{p.name}</td>
            <td>{p.price.toLocaleString()}</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

products가 1만 개 이상이거나 계산이 복잡할 때 효과적입니다. 단순한 필터링/정렬은 useMemo 없어도 충분히 빠릅니다.

🔄 useCallback — 함수 재생성 방지

컴포넌트가 렌더링될 때마다 내부에서 선언한 함수는 새로 생성됩니다. useCallback은 함수 자체를 메모이제이션합니다:

function Parent() {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')

  // ❌ 매 렌더마다 새 함수 생성 → ChildComponent가 불필요하게 리렌더
  const handleSomething = () => console.log('처리')

  // ✅ useCallback으로 함수 메모이제이션
  const handleSomethingMemo = useCallback(() => {
    console.log('처리')
  }, [])  // 의존성이 없으면 최초 한 번만 생성

  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      {/* ChildComponent는 handleSomethingMemo가 바뀔 때만 리렌더 */}
      <ChildComponent onAction={handleSomethingMemo} />
    </div>
  )
}

useCallback이 효과를 내려면 받는 쪽 컴포넌트가 React.memo로 감싸져 있어야 합니다. 그렇지 않으면 useCallback은 의미가 없습니다.

🧱 React.memo — 컴포넌트 메모이제이션

React.memo로 감싼 컴포넌트는 Props가 바뀌지 않으면 리렌더링하지 않습니다:

// React.memo로 감싸기
const ExpensiveItem = React.memo(function ExpensiveItem({
  name,
  price,
  onDelete,
}: {
  name: string
  price: number
  onDelete: (name: string) => void
}) {
  console.log(`${name} 렌더링`)  // memo 없으면 부모 리렌더마다 출력됨

  return (
    <div className="flex justify-between p-3 border-b">
      <span>{name} — {price.toLocaleString()}원</span>
      <button onClick={() => onDelete(name)}>삭제</button>
    </div>
  )
})

🔬 최적화 결정 체크리스트

  • React.DevTools Profiler로 실제로 느린 컴포넌트를 확인했는가?
  • 계산이 진짜 무겁거나(수천 개 이상의 데이터 처리), 함수가 자주 재생성되어 자식 리렌더를 일으키는가?
  • 최적화 후 실제로 빨라졌는가?

이 세 질문에 모두 "예"라면 적용합니다. 그렇지 않으면 코드를 단순하게 유지하세요.

✅ 정리

  • useMemo: 비싼 계산 결과를 캐싱. 의존성이 바뀔 때만 재계산.
  • useCallback: 함수를 메모이제이션. React.memo와 함께 써야 효과적.
  • React.memo: Props가 같으면 리렌더링 방지.
  • 측정 없는 최적화는 시기상조. 먼저 동작하게 만들고, 느리면 그때 최적화하세요.

관련 주제

  • reducer 개념
  • action dispatch
  • useState 대비 장점
  • 복잡한 상태 관리 시나리오
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

리액트 입문 — 컴포넌트로 만드는 웹

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...