← 목록으로

코드 최적화란?

코드 최적화 핵심 가이드

서론

코드 최적화는 애플리케이션의 성능, 유지보수성, 사용자 경험을 향상시키는 핵심 요소입니다. 이 글에서는 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);

결론

코드 최적화는 지속적인 과정입니다. 성능 측정 도구를 활용하여 병목 지점을 파악하고, 적절한 최적화 기법을 적용하면 더 빠르고 효율적인 애플리케이션을 구축할 수 있습니다.

핵심 원칙:

  • 측정 후 최적화: 성능 문제를 먼저 측정
  • 과도한 최적화 지양: 필요한 곳에만 최적화 적용
  • 가독성 유지: 최적화로 인한 코드 복잡도 증가 주의
  • 지속적인 모니터링: 성능 지표 추적