[REACT] 코드의 성능 최적화하기

Lim Jeong Hu·2025년 1월 24일
post-thumbnail

씨즌넷 프로젝트 장비 대여 페이지 컴포넌트를 만드는 중이었다.
아코디언 형식이라, 여러 기능이 필요한 컴포넌트였다. 근데 크게 신경을 쓰지 않고 모든 기능을 하나의 컴포넌트로 때려박았다.
그런데 순간 성능에 지장이 없을까 궁금해졌고, 성능을 최적화할 수 있는 기법을 찾아보고, 직접 적용한다면 어떻게 쓸지까지 조사하게 되었다.

React에서 더 나은 사용자 경험을 제공하고, 불필요한 리렌더링을 줄이기 위해 성능 최적화는 매우 중요하다. 이 글에서는 React의 몇 가지 성능 최적화 기법을 설명한 후, 실제 코드에 적용하며 설명하도록 하겠다.

1. React.memo

React.memo컴포넌트동일한 props로 다시 렌더링되는 것을 방지하여 성능을 향상시킨다. 주로 순수 컴포넌트에 사용되며, props가 변경되지 않는 한 이전 렌더링 결과를 재사용한다.

적용 전

/* 이전의 ListItem 컴포넌트는 props의 변화와 상관없이
부모 컴포넌트가 리렌더링될 때마다 무조건 리렌더된다. */
const ListItem: React.FC<ListItemProps> = ({ item }) => {
  console.log('Rendering:', item);
  return <div>{item}</div>;
};

const List: React.FC<{ items: string[] }> = ({ items }) => {
  return (
    <div>
      {items.map(item => <ListItem key={item} item={item} />)}
    </div>
  );
};

적용 후

// React.memo를 사용하여 props가 변경되지 않는 한 리렌더링을 방지한다.
const ListItem: React.FC<ListItemProps> = React.memo(({ item }) => {
  console.log('Rendering:', item);
  return <div>{item}</div>;
});

변경 설명: React.memoListItem 컴포넌트에 적용하여, item prop이 변경되지 않는 한 컴포넌트의 리렌더링을 방지한다. 이는 부모 컴포넌트인 List가 리렌더링되더라도 item prop이 같으면 ListItem의 리렌더링을 막아 성능을 개선한다.

성능 개선: 이 변경으로 인해 리스트의 각 항목이 데이터에 변화가 없다면, 불필요한 리렌더링이 발생하지 않는다.

2. useMemo

계산된 값을 메모이징하여 불필요한 연산을 줄인다. 여기서 메모이징이란 이전에 계산한 값을 저장함으로써 동일한 계산을 반복하지 않도록 하는 기술이다. 복잡한 계산이 필요한 값을 다룰 때 사용하면 효과적이다.

적용 전

const ExpensiveComponent: React.FC<{ items: number[] }> = ({ items }) => {
  // 이 코드에서는 배열을 매 리렌더링마다 정렬한다.
  const sortedItems = items.sort((a, b) => a - b);

  return (
    <div>
      {sortedItems.map(item => <div key={item}>{item}</div>)}
    </div>
  );
};

적용 후

const ExpensiveComponent: React.FC<{ items: number[] }> = ({ items }) => {
// useMemo를 사용하여 정렬 연산을 메모이징한다.
  const sortedItems = useMemo(() => {
    console.log('Sorting items');
    return items.sort((a, b) => a - b);
  }, [items]);

  return (
    <div>
      {sortedItems.map(item => <div key={item}>{item}</div>)}
    </div>
  );
};

변경 설명: useMemo를 사용하여 배열을 정렬하는 복잡한 연산을 메모이징한다. items 배열이 변경될 때만 정렬 연산이 실행되며, 그 외의 경우에는 메모리에 저장된 값을 재사용한다.

성능 개선: 이 접근 방식은 배열 처리에 필요한 계산량을 크게 줄여준다. 리렌더링 시점에 계산된 결과를 즉시 사용할 수 있어 최적화가 가능하다.

3. useCallback

특정 함수를 메모이징하여 컴포넌트가 리렌더링될 때마다 같은 함수를 새로 생성하지 않도록 한다. 이는 특히 이벤트 핸들러나 props로 전달되는 함수에 유용하다.

적용 전

const Counter: React.FC = () => {
  const [count, setCount] = useState(0);

  // 이벤트 핸들러는 컴포넌트가 리렌더링될 때마다 새로 생성된다.
  const increment = () => {
    console.log('Incrementing count');
    setCount(c => c + 1);
  };

  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

적용 후

const Counter: React.FC = () => {
  const [count, setCount] = useState(0);

// useCallback을 사용하여 increment 함수를 메모이징한다.
  const increment = useCallback(() => {
    console.log('Incrementing count');
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

변경 설명: useCallback을 사용하여 increment 함수를 메모이징한다. 이 함수는 의존성 배열이 비어 있기 때문에 컴포넌트의 리렌더링에 영향을 받지 않고 동일한 참조를 유지한다.

성능 개선: 이 변경으로 인해 increment 함수는 한 번 생성된 후 재사용되므로, 함수를 자주 생성하는 오버헤드가 제거된다. 이는 리렌더링 효율성을 높이고 메모리 사용을 최적화한다.

결론

아직은 익숙하지 않지만, 쓰는 데 신경을 쓰다 보면 UX가 더 좋은 웹사이트를 만드는 데 도움이 될 것 같다. 각 기법을 적용해보며, 최적화의 방법을 어느 정도 익힌 것 같아서 뿌듯하다.

profile
종강주세요

1개의 댓글

comment-user-thumbnail
2025년 1월 24일

와 이렇게 어려운 내용을 벌써 공부하다니 과연 에이스답다 다음번엔 이걸 실제로 적용한 예시와 최적화했다는 건 어떻게 확인하는지 방법들도 찾아보고 글 써봤으면 짱짱맨일듯요~! 고생했어요👍🏻

답글 달기