← 가이드 목록으로 ← Back to guides

React 성능 최적화와 고급 패턴 완벽 가이드 React Performance Optimization & Advanced Patterns Guide

React 앱이 커질수록 성능 최적화가 중요해집니다. React.memo, 코드 스플리팅, Suspense를 활용해 사용자 경험을 극대화하는 방법을 배워봅시다.

As React apps grow, performance optimization becomes crucial. Let's learn how to maximize user experience with React.memo, code splitting, and Suspense.

React.memo 심화 React.memo Deep Dive

React.memo는 props가 변경되지 않으면 리렌더링을 건너뛰는 고차 컴포넌트입니다.

React.memo is a higher-order component that skips re-rendering if props haven't changed.

// 기본 사용법
const ExpensiveList = React.memo(({ items }) => {
  console.log('리스트 렌더링');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

// 커스텀 비교 함수
const UserCard = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    // true 반환 시 리렌더링 건너뜀
    return prevProps.user.id === nextProps.user.id;
  }
);
// Basic usage
const ExpensiveList = React.memo(({ items }) => {
  console.log('List rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

// Custom comparison function
const UserCard = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    // Return true to skip re-render
    return prevProps.user.id === nextProps.user.id;
  }
);

코드 스플리팅과 lazy 로딩 Code Splitting & Lazy Loading

React.lazySuspense로 필요한 시점에 컴포넌트를 로드하여 초기 번들 크기를 줄입니다.

Use React.lazy and Suspense to load components on demand and reduce initial bundle size.

import { lazy, Suspense } from 'react';

// 동적 import로 컴포넌트 분리
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const Profile = lazy(() => import('./Profile'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}
import { lazy, Suspense } from 'react';

// Split components with dynamic import
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const Profile = lazy(() => import('./Profile'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}

useDeferredValue와 useTransition useDeferredValue & useTransition

React 18의 동시성 기능으로 UI 응답성을 유지하면서 비용이 큰 업데이트를 처리합니다.

React 18's concurrent features handle expensive updates while maintaining UI responsiveness.

import { useState, useDeferredValue, useTransition } from 'react';

// useDeferredValue: 긴급하지 않은 값 지연
function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <ExpensiveList query={deferredQuery} />
    </div>
  );
}

// useTransition: 로딩 상태와 함께 전환
function TabPanel() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('home');

  const handleTabChange = (newTab) => {
    startTransition(() => {
      setTab(newTab);
    });
  };

  return (
    <>
      {isPending && <Spinner />}
      <TabContent tab={tab} />
    </>
  );
}
import { useState, useDeferredValue, useTransition } from 'react';

// useDeferredValue: Defer non-urgent values
function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <ExpensiveList query={deferredQuery} />
    </div>
  );
}

// useTransition: Transition with loading state
function TabPanel() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('home');

  const handleTabChange = (newTab) => {
    startTransition(() => {
      setTab(newTab);
    });
  };

  return (
    <>
      {isPending && <Spinner />}
      <TabContent tab={tab} />
    </>
  );
}

가상화 (Virtualization) Virtualization

수천 개의 아이템을 렌더링할 때는 화면에 보이는 부분만 렌더링하는 가상화를 적용합니다.

When rendering thousands of items, apply virtualization to render only visible portions.

// react-window 사용 예시
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}
// react-window example
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}

✨ 성능 최적화 체크리스트 ✨ Performance Optimization Checklist

  • 프로파일러로 병목 지점 확인
  • React.memo로 불필요한 리렌더링 방지
  • lazy로 라우트별 코드 분리
  • 가상화로 대량 리스트 최적화
  • useTransition으로 UI 블로킹 방지
  • Use Profiler to identify bottlenecks
  • Prevent unnecessary re-renders with React.memo
  • Split code by route with lazy
  • Optimize large lists with virtualization
  • Prevent UI blocking with useTransition

⚠️ 흔한 실수들 ⚠️ Common Mistakes

  • 측정 없이 최적화 적용
  • 모든 컴포넌트에 React.memo 사용
  • 인라인 함수/객체로 memo 무효화
  • key로 인덱스 사용
  • Optimizing without measurement
  • Using React.memo on every component
  • Invalidating memo with inline functions/objects
  • Using index as key