react-performance-optimization은(는) 무엇을 하나요?
React 성능 최적화 패턴 – 메모이제이션, 코드 분할, 효율적인 렌더링 전략을 사용합니다. 느린 React 애플리케이션을 최적화하거나 번들 크기를 줄이거나 대용량 데이터셋을 사용하는 사용자 경험을 개선할 때 사용하세요.
개요
이 스킬은 React 컴포넌트 렌더링 성능을 개선하기 위한 입증된 패턴과 모범 사례를 제공합니다. 주요 최적화 기법은 다음과 같습니다:
- 메모이제이션: 불필요한 재렌더링 방지 (
React.memo,useMemo,useCallback) - 코드 분할: 초기 번들 크기 줄이기 (
React.lazy,Suspense, 동적import()) - 가상화: 큰 목록/테이블에 필요한 DOM 노드만 렌더링 (
react-window,react-virtualized) - 상태 관리 최적화: 리렌더링을 최소화하는 상태 구조화 (상태 끌어올리기, 컨텍스트 분할)
- 렌더링 사이클 이해:
useEffect,useLayoutEffect, 동시성 기능 (startTransition, useDeferredValue) - 프로파일링 도구: React DevTools, 번들 분석기 (
webpack-bundle-analyzer,vite-plugin-inspect)
시작하기
성능 최적화를 적용하기 전에 프로파일링으로 병목 지점을 파악하세요. 이 스킬의 접근 방식은 측정(measurement), 최적화(optimization), 검증(verification)의 반복이며, 성급한 최적화를 지양하고 데이터 기반 의사 결정을 강조합니다.
설치
이 스킬은 특정 패키지 설치가 필요하지 않습니다. 필요한 경우 권장 라이브러리들을 설치하세요:
# 가상화 (필요 시)
npm install react-window react-virtualized-auto-sizer
# 번들 분석 (필요 시)
npm install --save-dev vite-plugin-inspect rollup-plugin-visualizer
사용법
React DevTools Profiler로 컴포넌트 트리의 렌더링 시간을 측정합니다:
import { Profiler } from 'react'
function onRenderCallback(
id, // 발생한 커밋의 프로파일 트리 ID
phase, // "mount" 또는 "update"
actualDuration, // 커밋에 소요된 실제 시간
baseDuration, // 하위 항목이 메모이제이션되지 않은 경우 예상 시간
startTime, // React가 이 커밋을 렌더링하기 시작한 시간
commitTime, // 이 커밋이 커밋된 시간
interactions // 이 업데이트에 속한 상호작용 집합
) {
if (actualDuration > 16) { // 60fps = ~16ms
console.warn(`느린 렌더링 감지: ${id} (${actualDuration.toFixed(2)}ms)`)
}
}
<Profiler id="Navigation" onRender={onRenderCallback}>
<Navigation />
</Profiler>
핵심 패턴
1. 메모이제이션
React.memo를 사용하여 prop이 변경되지 않은 경우 컴포넌트의 재렌더링을 방지하세요:
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onSelect(item.id)}>
{item.name}
</li>
))}
</ul>
)
})
useCallback과 useMemo로 props 안정성을 유지하세요:
function SearchResults({ query }) {
const filteredItems = useMemo(
() => items.filter(item => item.name.includes(query)),
[query]
)
const handleSelect = useCallback((id) => {
console.log('선택됨:', id)
}, []) // 의존성이 없으므로 안정적인 참조 유지
return <ExpensiveList items={filteredItems} onSelect={handleSelect} />
}
2. 코드 분할
라우트 레벨에서 코드를 분할하여 초기 다운로드 크기를 줄이세요:
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
export default function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
)
}
3. 가상화
큰 목록(수백/수천 개 항목)에는 react-window를 사용하세요:
import { FixedSizeList as List } from 'react-window'
import AutoSizer from 'react-virtualized-auto-sizer'
function VirtualizedList({ items }) {
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
itemCount={items.length}
itemSize={50} // 각 행의 높이 (픽셀)
width={width}
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</List>
)}
</AutoSizer>
)
}
4. 동시성 기능
React 18의 동시성 기능으로 urgent 업데이트의 응답성을 유지하세요:
import { startTransition, useDeferredValue } from 'react'
function SearchPage() {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
// 느린 필터는 지연된 값 사용
const searchResults = useMemo(
() => filterItems(allItems, deferredQuery),
[deferredQuery]
)
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<Suspense fallback={<div>검색 중...</div>}>
<SearchResults results={searchResults} />
</Suspense>
</>
)
}
5. 배열 키 최적화
리스트를 렌더링할 때는 안정적이고 고유한 key prop을 사용하세요. 인덱스를 키로 사용하지 마세요:
// 좋지 않음 (인덱스 사용)
{items.map((item, index) => <Item key={index} item={item} />)}
// 좋음 (고유 ID 사용)
{items.map(item => <Item key={item.id} item={item} />)}
성능 프로파일링
최적화를 적용하기 전과 후에 성능을 측정하세요:
- Chrome DevTools Performance 탭: 프레임 드롭 감지
- React DevTools Profiler: 개별 컴포넌트 렌더링 시간 확인
- 번들 분석기: 코드 중복 및 거대한 의존성 식별
- Vite:
vite-plugin-inspect또는rollup-plugin-visualizer - CRA/Webpack:
webpack-bundle-analyzer
- Vite:
모범 사례 요약
- 측정 후 최적화: 가정하지 말고 프로파일러로 확인하세요.
- 컴포넌트 분할하기: 자주 변경되는 상태를 별도의 작은 컴포넌트로 분리하세요.
- 컨텍스트 주의: 컨텍스트 값이 변경되면 모든 소비자가 리렌더링됩니다. 필요할 때만 분할하세요.
- 불변성 유지: 배열은
concat/spread, 객체는 spread를 사용하여 예기치 않은 리렌더링을 방지하세요. - 프레임워크 기능 활용: Next.js 이미지 최적화, 스트리밍 SSR, 부분 Pre-rendering을 사용하세요.
면책사항
이 스킬은 코딩 에이전트를 위한 가이드입니다. 일부 최적화는 복잡성을 증가시킬 수 있습니다(예: 메모이제이션은 가비지 컬렉션 압력을 증가시킴). 측정 가능한 이점이 있을 때만 적용하세요.