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;
}
);
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.lazy와 Suspense로 필요한 시점에 컴포넌트를 로드하여 초기 번들 크기를 줄입니다.
Use React.lazy and Suspense to load components on demand and reduce
initial bundle size.
// 동적 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>
);
}
// 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.
// 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} />
</>
);
}
// 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.
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>
);
}
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.memoon every component - Invalidating
memowith inline functions/objects - Using index as
key
