React는 강력한 프론트엔드 라이브러리지만, 애플리케이션의 규모가 커지면 성능 이슈가 발생할 수 있습니다. 이 문서에서는 React 애플리케이션의 성능을 최적화하는 다양한 기법과 전략을 살펴봅니다.
컴포넌트의 props가 변경되지 않았을 때 리렌더링을 방지합니다.
const MyComponent = React.memo(function MyComponent(props) {
/* 렌더링 로직 */
});
특정 props만 비교하고 싶다면 두 번째 인자에 커스텀 비교 함수를 전달할 수 있습니다:
const MyComponent = React.memo(
function MyComponent(props) {
/* 렌더링 로직 */
},
(prevProps, nextProps) => {
// true를 반환하면 리렌더링을 방지합니다
return prevProps.id === nextProps.id;
}
);
계산 비용이 많이 드는 연산이나 자식 컴포넌트에 전달되는 함수를 메모이제이션합니다.
// 계산 비용이 많이 드는 값 메모이제이션
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// 자식 컴포넌트에 전달되는 함수 메모이제이션
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
필요한 시점에 컴포넌트를 동적으로 로드합니다.
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
라우팅 라이브러리(React Router)와 함께 활용하여 각 페이지 단위로 코드를 분할합니다.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const Contact = lazy(() => import('./routes/Contact'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</Router>
);
}
애플리케이션 전체에서 필요한 상태만 전역 상태로 관리하고, 컴포넌트 로컬 상태를 적절히 활용합니다.
Context API를 사용할 때 모든 상태를 하나의 Context에 넣지 말고, 논리적으로 분리하여 필요한 부분만 리렌더링되도록 합니다.
// 여러 개의 작은 Context 생성
const ThemeContext = React.createContext();
const UserContext = React.createContext();
const SettingsContext = React.createContext();
function App() {
// 각 Context의 Provider를 중첩하여 사용
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<SettingsContext.Provider value={settings}>
<MainApp />
</SettingsContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
대량의 데이터를 렌더링할 때 화면에 보이는 항목만 렌더링하여 성능을 개선합니다.
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={500}
width={300}
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}
화면에 보이는 이미지만 로드합니다.
import { LazyLoadImage } from 'react-lazy-load-image-component';
function MyImage({ src, alt }) {
return (
<LazyLoadImage
src={src}
alt={alt}
effect="blur"
placeholderSrc={smallImageSrc}
/>
);
}
WebP와 같은 최신 이미지 포맷을 사용하고, 필요에 맞는 적절한 크기로 이미지를 제공합니다.
<picture>
<source srcSet="image.webp" type="image/webp" />
<source srcSet="image.jpg" type="image/jpeg" />
<img src="image.jpg" alt="Description" width="800" height="600" />
</picture>
React 개발자 도구의 Profiler 탭을 사용하여 컴포넌트의 렌더링 시간과 빈도를 측정합니다.
Google의 Lighthouse를 사용하여 웹사이트의 전반적인 성능, 접근성, SEO 등을 측정합니다.
Core Web Vitals(LCP, FID, CLS)를 측정하여 사용자 경험 지표를 추적합니다.
import { getCLS, getFID, getLCP } from 'web-vitals';
function sendToAnalytics(metric) {
const { name, value } = metric;
console.log(`${name}: ${value}`);
// 분석 서비스로 데이터 전송
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
React 애플리케이션 성능 최적화는 단일 기법이 아닌 여러 기법의 조합으로 이루어집니다. 항상 측정 가능한 지표를 기반으로 최적화하고, 실제 사용자 경험 향상에 초점을 맞추세요. 모든 최적화 기법을 동시에 적용하기보다는 가장 큰 성능 향상을 가져올 수 있는 부분부터 점진적으로 적용하는 것이 효과적입니다.