React 성능 최적화 완벽 가이드
1. 도입 – 학습 목표 및 필요성
🔗 관련 에러 해결 가이드
이 React 성능 최적화 완벽 가이드는 React 애플리케이션의 성능을 극대화하고 사용자 경험을 개선하는 방법을 단계별로 알려드립니다. 현대 웹 애플리케이션에서 성능은 사용자 만족도와 직결되며, 특히 대규모 React 프로젝트에서는 적절한 최적화 없이는 렌더링 지연, 메모리 누수, 느린 응답 시간 등의 문제가 발생할 수 있습니다. 본 가이드를 통해 React의 핵심 최적화 기법인 메모이제이션, 코드 스플리팅, 가상화, 그리고 렌더링 최적화를 마스터하여 빠르고 효율적인 애플리케이션을 구축할 수 있습니다.
2. 기본 개념 설명
React 성능 최적화를 이해하기 위해서는 먼저 React의 렌더링 메커니즘을 알아야 합니다. React는 가상 DOM(Virtual DOM)을 사용하여 실제 DOM 조작을 최소화하지만, 불필요한 컴포넌트 재렌더링은 여전히 성능 병목을 유발할 수 있습니다.
주요 개념:
- 리렌더링(Re-rendering): 컴포넌트의 state나 props가 변경될 때 컴포넌트가 다시 렌더링되는 과정
- 메모이제이션(Memoization): 이전 계산 결과를 캐싱하여 동일한 입력에 대해 재계산을 방지하는 기법
- 코드 스플리팅(Code Splitting): 번들 크기를 줄이기 위해 코드를 여러 청크로 분할하는 방법
- 레이지 로딩(Lazy Loading): 필요한 시점에만 컴포넌트나 리소스를 로드하는 전략
- 가상화(Virtualization): 긴 리스트에서 화면에 보이는 항목만 렌더링하는 기술
3. 단계별 구현 가이드
단계 1: React.memo로 불필요한 렌더링 방지
React.memo는 고차 컴포넌트(HOC)로, props가 변경되지 않으면 컴포넌트의 재렌더링을 방지합니다. 특히 자식 컴포넌트가 부모 컴포넌트의 state 변경과 무관할 때 유용합니다.
적용 시나리오: 부모 컴포넌트가 자주 업데이트되지만 자식 컴포넌트는 특정 props에만 의존하는 경우, React.memo를 사용하여 자식 컴포넌트의 불필요한 렌더링을 차단할 수 있습니다.
단계 2: useMemo와 useCallback 훅 활용
useMemo는 계산 비용이 높은 연산 결과를 메모이제이션하고, useCallback은 함수 자체를 메모이제이션합니다. 이를 통해 매 렌더링마다 새로운 값이나 함수가 생성되는 것을 방지할 수 있습니다.
사용 원칙:
- useMemo: 복잡한 계산, 필터링, 정렬 등의 연산 결과를 캐싱
- useCallback: 자식 컴포넌트에 전달되는 콜백 함수를 메모이제이션하여 자식의 불필요한 렌더링 방지
- 의존성 배열을 정확히 지정하여 메모이제이션이 올바르게 작동하도록 관리
단계 3: 코드 스플리팅과 Lazy Loading 구현
React.lazy와 Suspense를 사용하여 라우트 기반 또는 컴포넌트 기반 코드 스플리팅을 구현합니다. 이는 초기 번들 크기를 줄이고 페이지 로딩 속도를 개선합니다.
구현 전략:
- 페이지별 코드 스플리팅: 각 라우트를 별도의 청크로 분리
- 조건부 로딩: 특정 조건에서만 필요한 컴포넌트를 동적으로 import
- Suspense의 fallback을 활용하여 로딩 상태 표시
단계 4: 리스트 가상화 적용
react-window 또는 react-virtualized 라이브러리를 사용하여 긴 리스트의 성능을 최적화합니다. 수천 개의 항목이 있는 리스트에서 화면에 보이는 항목만 렌더링하여 DOM 노드 수를 크게 줄일 수 있습니다.
단계 5: 개발자 도구로 성능 측정
React DevTools의 Profiler와 Chrome DevTools의 Performance 탭을 활용하여 렌더링 성능을 측정하고 병목 지점을 식별합니다. Lighthouse를 사용하여 전반적인 웹 성능 지표도 확인합니다.
4. 실제 코드 예제와 설명
예제 1: React.memo 최적화
// 최적화 전
const ChildComponent = ({ data }) => {
console.log('렌더링됨');
return {data.name};
};
// 최적화 후
const ChildComponent = React.memo(({ data }) => {
console.log('렌더링됨');
return {data.name};
});
// 커스텀 비교 함수 사용
const ChildComponent = React.memo(
({ data }) => {data.name},
(prevProps, nextProps) => prevProps.data.id === nextProps.data.id
);
예제 2: useMemo와 useCallback
import { useMemo, useCallback, useState } from 'react';
const ExpensiveComponent = ({ items }) => {
const [filter, setFilter] = useState('');
// 비싼 계산 메모이제이션
const filteredItems = useMemo(() => {
console.log('필터링 계산 실행');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
// 콜백 함수 메모이제이션
const handleClick = useCallback((id) => {
console.log('클릭:', id);
}, []);
return (
setFilter(e.target.value)} />
{filteredItems.map(item => (
))}
);
};
예제 3: 코드 스플리팅
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy Loading으로 컴포넌트 import
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
로딩 중...