코드 최적화란?
코드 최적화 핵심 가이드
서론
코드 최적화는 애플리케이션의 성능, 유지보수성, 사용자 경험을 향상시키는 핵심 요소입니다. 이 글에서는 React와 Next.js를 중심으로 실용적인 코드 최적화 기법을 간략하게 정리합니다.
1. React 컴포넌트 최적화
1.1 React.memo
불필요한 리렌더링 방지:
import { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => {
return <div>{data}</div>;
});
export default ExpensiveComponent;
1.2 useMemo와 useCallback
비용이 큰 계산과 함수 메모이제이션:
import { useMemo, useCallback } from 'react';
function MyComponent({ items, filter }) {
// 비용이 큰 계산 메모이제이션
const filteredItems = useMemo(() => {
return items.filter(item => item.category === filter);
}, [items, filter]);
// 함수 메모이제이션
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return (
<div>
{filteredItems.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</div>
))}
</div>
);
}
1.3 코드 스플리팅
동적 import로 번들 크기 감소:
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Loading...</p>,
ssr: false,
});
export default function Page() {
return <HeavyComponent />;
}
2. 상태 관리 최적화
2.1 상태 구조 최적화
불필요한 리렌더링 방지를 위한 상태 분리:
// 나쁜 예: 하나의 상태 객체
const [state, setState] = useState({
name: '',
email: '',
count: 0,
});
// 좋은 예: 관련 없는 상태 분리
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [count, setCount] = useState(0);
2.2 Context 최적화
Context를 여러 개로 분리:
// 나쁜 예: 하나의 큰 Context
const AppContext = createContext();
// 좋은 예: 기능별 Context 분리
const UserContext = createContext();
const ThemeContext = createContext();
const CartContext = createContext();
3. 렌더링 최적화
3.1 조건부 렌더링 최적화
// 나쁜 예: 항상 평가됨
{items.length > 0 && <List items={items} />}
// 좋은 예: 조건 확인 후 렌더링
{items.length > 0 ? <List items={items} /> : <EmptyState />}
3.2 리스트 렌더링 최적화
import { memo } from 'react';
const ListItem = memo(({ item }) => {
return <div>{item.name}</div>;
});
function List({ items }) {
return (
<div>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</div>
);
}
4. Next.js 최적화
4.1 정적 생성 (SSG)
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 3600, // ISR: 1시간마다 재생성
};
}
4.2 서버 컴포넌트 활용
// app/components/ServerComponent.js
async function ServerComponent() {
const data = await fetch('https://api.example.com/data');
return <div>{data}</div>;
}
export default ServerComponent;
4.3 이미지 최적화
import Image from 'next/image';
export default function OptimizedImage() {
return (
<Image
src="/image.jpg"
alt="Description"
width={1200}
height={800}
loading="lazy"
sizes="(max-width: 768px) 100vw, 50vw"
/>
);
}
5. 번들 크기 최적화
5.1 Tree Shaking
// 나쁜 예: 전체 라이브러리 import
import _ from 'lodash';
// 좋은 예: 필요한 것만 import
import debounce from 'lodash/debounce';
5.2 외부 라이브러리 최소화
// 나쁜 예: 큰 라이브러리 사용
import moment from 'moment';
// 좋은 예: 가벼운 대안 사용
import { format } from 'date-fns';
5.3 동적 import
// 필요할 때만 로드
const handleClick = async () => {
const module = await import('./heavyModule');
module.doSomething();
};
6. 네트워크 최적화
6.1 API 요청 최적화
import { useQuery } from '@tanstack/react-query';
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5분간 캐시
cacheTime: 10 * 60 * 1000, // 10분간 유지
});
}
6.2 배치 요청
// 나쁜 예: 여러 번 요청
users.forEach(user => fetchUserData(user.id));
// 좋은 예: 한 번에 요청
const userIds = users.map(user => user.id);
fetchUsersData(userIds);
7. 메모리 최적화
7.1 이벤트 리스너 정리
import { useEffect } from 'react';
function Component() {
useEffect(() => {
const handleScroll = () => {
// 스크롤 처리
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
}
7.2 타이머 정리
import { useEffect } from 'react';
function Component() {
useEffect(() => {
const timer = setInterval(() => {
// 주기적 작업
}, 1000);
return () => clearInterval(timer);
}, []);
}
8. 알고리즘 최적화
8.1 불필요한 반복 제거
// 나쁜 예: O(n²)
const result = items.map(item => {
return items.find(other => other.id === item.parentId);
});
// 좋은 예: O(n)
const map = new Map(items.map(item => [item.id, item]));
const result = items.map(item => map.get(item.parentId));
8.2 조기 반환
// 나쁜 예: 중첩된 조건문
function processData(data) {
if (data) {
if (data.items) {
if (data.items.length > 0) {
return data.items.map(process);
}
}
}
return [];
}
// 좋은 예: 조기 반환
function processData(data) {
if (!data?.items?.length) return [];
return data.items.map(process);
}
9. 코드 품질 최적화
9.1 함수 분리
// 나쁜 예: 긴 함수
function handleSubmit(e) {
e.preventDefault();
const data = new FormData(e.target);
const values = Object.fromEntries(data);
const validated = validate(values);
if (validated) {
fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(values),
});
}
}
// 좋은 예: 작은 함수로 분리
function handleSubmit(e) {
e.preventDefault();
const values = extractFormData(e.target);
if (validateForm(values)) {
submitForm(values);
}
}
9.2 상수 추출
// 나쁜 예: 매직 넘버
setTimeout(() => {}, 3000);
// 좋은 예: 의미 있는 상수
const DELAY_MS = 3000;
setTimeout(() => {}, DELAY_MS);
10. 성능 측정
10.1 React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log('Component:', id, 'Phase:', phase, 'Duration:', actualDuration);
}
<Profiler id="App" onRender={onRenderCallback}>
<App />
</Profiler>
10.2 Web Vitals 측정
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
console.log(metric);
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
결론
코드 최적화는 지속적인 과정입니다. 성능 측정 도구를 활용하여 병목 지점을 파악하고, 적절한 최적화 기법을 적용하면 더 빠르고 효율적인 애플리케이션을 구축할 수 있습니다.
핵심 원칙:
- 측정 후 최적화: 성능 문제를 먼저 측정
- 과도한 최적화 지양: 필요한 곳에만 최적화 적용
- 가독성 유지: 최적화로 인한 코드 복잡도 증가 주의
- 지속적인 모니터링: 성능 지표 추적