React 성능 최적화 완벽 가이드
1. 도입 – 학습 목표 및 필요성
🔗 관련 에러 해결 가이드
이 React 성능 최적화 완벽 가이드는 React 애플리케이션의 성능을 극대화하고 사용자 경험을 개선하는 실전 기법들을 다룹니다. 현대 웹 애플리케이션에서 성능은 단순히 빠른 로딩 속도를 넘어 사용자 유지율과 전환율에 직접적인 영향을 미칩니다. 이 가이드를 통해 불필요한 리렌더링 방지, 메모이제이션 활용, 코드 스플리팅, 가상화 기법 등 실무에서 즉시 적용 가능한 최적화 전략들을 배우게 됩니다. 성능 병목 지점을 식별하고 해결하는 방법을 익히면, 더 빠르고 효율적인 React 애플리케이션을 개발할 수 있습니다.
2. 기본 개념 설명
React 성능 최적화의 핵심은 불필요한 렌더링을 최소화하는 것입니다. React는 상태나 props가 변경될 때마다 컴포넌트를 다시 렌더링하는데, 이 과정에서 Virtual DOM 비교 및 실제 DOM 업데이트가 발생합니다. 주요 최적화 개념으로는 첫째, 메모이제이션이 있습니다. React.memo, useMemo, useCallback을 활용해 계산 결과나 함수를 캐싱합니다. 둘째, 코드 스플리팅으로 초기 번들 크기를 줄여 로딩 속도를 개선합니다. 셋째, 가상화(Virtualization)로 긴 리스트를 효율적으로 렌더링합니다. 넷째, Lazy Loading으로 필요한 시점에만 컴포넌트를 로드합니다. 마지막으로 성능 측정을 위해 React DevTools Profiler와 Lighthouse를 활용합니다. 이러한 개념들을 이해하고 적재적소에 적용하는 것이 성능 최적화의 기본입니다.
3. 단계별 구현 가이드
단계 1: 성능 병목 지점 식별하기
최적화를 시작하기 전에 먼저 성능 문제가 어디에서 발생하는지 파악해야 합니다. React DevTools Profiler를 설치하고 애플리케이션을 실행하면서 각 컴포넌트의 렌더링 시간을 측정하세요. Profiler 탭에서 ‘Start Profiling’ 버튼을 클릭하고 사용자 인터랙션을 수행한 후 분석합니다. 렌더링 시간이 긴 컴포넌트나 불필요하게 자주 렌더링되는 컴포넌트를 찾아냅니다. Chrome DevTools의 Performance 탭도 활용하여 JavaScript 실행 시간과 레이아웃 변경 사항을 확인합니다.
단계 2: React.memo로 불필요한 리렌더링 방지
부모 컴포넌트가 리렌더링될 때 자식 컴포넌트도 함께 렌더링되는 것이 React의 기본 동작입니다. 하지만 props가 변경되지 않았다면 자식 컴포넌트를 다시 렌더링할 필요가 없습니다. React.memo는 props를 얕은 비교(shallow comparison)하여 변경되지 않았으면 이전 렌더링 결과를 재사용합니다. 특히 리스트 아이템, 복잡한 UI 컴포넌트, 자주 렌더링되지만 props 변경이 적은 컴포넌트에 적용하면 효과적입니다. 단, 모든 컴포넌트에 무분별하게 적용하면 오히려 비교 비용이 증가할 수 있으므로 성능 측정 후 선택적으로 적용해야 합니다.
단계 3: useMemo와 useCallback 활용
useMemo는 계산 비용이 큰 연산 결과를 캐싱하고, useCallback은 함수 참조를 유지합니다. 특히 useCallback은 자식 컴포넌트에 콜백 함수를 props로 전달할 때 필수적입니다. 함수가 매번 새로 생성되면 React.memo로 감싼 자식 컴포넌트도 리렌더링되기 때문입니다. useMemo는 필터링, 정렬, 복잡한 계산 등에 사용합니다. 의존성 배열을 정확하게 설정하는 것이 중요하며, eslint-plugin-react-hooks를 설치하면 의존성 누락을 방지할 수 있습니다.
단계 4: 코드 스플리팅 적용
React.lazy와 Suspense를 사용하여 컴포넌트를 동적으로 import합니다. 라우트 기반 코드 스플리팅이 가장 효과적이며, 각 페이지를 별도의 청크로 분리합니다. Webpack의 Magic Comments를 활용하면 청크 이름을 지정하고 prefetch/preload 전략을 설정할 수 있습니다. Bundle Analyzer를 사용해 번들 크기를 시각화하고 큰 라이브러리를 식별합니다. 필요하다면 대체 라이브러리로 교체하거나 tree-shaking을 적용합니다.
단계 5: 가상화로 긴 리스트 최적화
수백 개 이상의 아이템을 렌더링할 때는 react-window 또는 react-virtualized 라이브러리를 사용합니다. 가상화는 화면에 보이는 아이템만 렌더링하고 스크롤에 따라 동적으로 교체하는 기법입니다. FixedSizeList, VariableSizeList 등 다양한 컴포넌트를 제공하며, 수천 개의 아이템도 부드럽게 스크롤할 수 있습니다. 무한 스크롤과 결합하면 대용량 데이터도 효율적으로 처리할 수 있습니다.
4. 실제 코드 예제와 설명
예제 1: React.memo와 useCallback 조합
import React, { useState, useCallback, memo } from 'react';
// React.memo로 최적화된 자식 컴포넌트
const ListItem = memo(({ item, onDelete }) => {
console.log('ListItem 렌더링:', item.id);
return (
{item.name}
);
});
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, name: 'React 학습' },
{ id: 2, name: '성능 최적화' }
]);
// useCallback으로 함수 참조 유지
const handleDelete = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
return (
{todos.map(todo => (
))}
);
}
export default TodoList;
예제 2: useMemo로 비용이 큰 계산 최적화
import React, { useMemo, useState } from 'react';
function ExpensiveComponent({ items }) {
const [filter, setFilter] = useState('');
// 필터링과 정렬을 메모이제이션
const processedItems = useMemo(() => {
console.log('복잡한 계산 실행');
return items
.filter(item => item.name.includes(filter))
.sort((a, b) => a.price - b.price);
}, [items, filter]);
return (
setFilter(e.target.value)}
placeholder="검색..."
/>
{processedItems.map(item => (
{item.name} - {item.price}원
))}
);
}
예제 3: 코드 스플리팅과 Lazy Loading
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// 동적 import로 코드 스플리팅
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'));
const Profile = lazy(() => import(/* webpackChunkName: "profile" */ './pages/Profile'));
function App() {
return (
로딩 중...