본문 바로가기
React

React에서 함수형 컴포넌트 성능 최적화하기

by 굿센스굿 2024. 12. 12.
반응형

 

React는 현재 가장 인기 있는 JavaScript 라이브러리 중 하나로, 동적이고 복잡한 사용자 인터페이스를 구축하는 데에 널리 사용되고 있습니다. 특히 함수형 컴포넌트는 React의 최신 기능으로, 더 간결하고 가독성이 높은 코드 작성이 가능하며, React의 성능을 최적화하는 데에도 중요한 역할을 합니다. 그러나 함수형 컴포넌트를 사용한다고 해서 자동으로 성능이 최적화되는 것은 아닙니다. 오히려 비효율적인 렌더링이나 불필요한 재렌더링이 발생할 수 있습니다. 이 글에서는 React에서 함수형 컴포넌트의 성능을 최적화하는 다양한 방법을 다룰 것입니다.

1. React 렌더링 성능의 기본 이해

먼저 React에서 성능을 최적화하려면, React의 렌더링 과정에 대한 기본적인 이해가 필요합니다. React는 **상태(state)**가 변경되거나 **속성(props)**이 업데이트되면, 해당 컴포넌트를 다시 렌더링합니다. 이때, 컴포넌트는 Virtual DOM을 사용하여 실제 DOM과 비교 후, 최소한의 변경사항만을 실제 DOM에 반영합니다.

하지만, 컴포넌트가 불필요하게 재렌더링되는 경우, 성능 저하가 발생할 수 있습니다. 예를 들어, 부모 컴포넌트가 상태를 변경하면 그 자식 컴포넌트들이 모두 재렌더링될 수 있습니다. 이때 성능을 최적화하려면, 불필요한 렌더링을 방지하는 방법을 고려해야 합니다.

2. React에서 성능 최적화를 위한 주요 기법

2.1. React.memo 사용

React에서는 memoization을 활용하여 성능을 최적화할 수 있습니다. 함수형 컴포넌트의 경우, React.memo를 사용하면 컴포넌트가 동일한 props를 받을 때 다시 렌더링하지 않고, 이전에 렌더링된 결과를 재사용합니다.

사용 예시

const MyComponent = React.memo((props) => {
  console.log('렌더링됨!');
  return <div>{props.value}</div>;
});

위 예시에서 MyComponent는 props.value가 변경되지 않는 한 다시 렌더링되지 않습니다. React.memo는 기본적으로 얕은 비교를 사용하여 props의 변경 여부를 판단하기 때문에, 성능 최적화가 가능하지만, 깊은 비교가 필요한 경우에는 사용자 정의 비교 함수를 제공할 수 있습니다.

사용자 정의 비교 함수

const MyComponent = React.memo((props) => {
  console.log('렌더링됨!');
  return <div>{props.value}</div>;
}, (prevProps, nextProps) => {
  return prevProps.value === nextProps.value; // 값이 동일하면 렌더링하지 않음
});

2.2. useMemo와 useCallback 활용

useMemo

useMemo는 값을 메모이제이션하여, 의존성 배열이 변경되지 않으면 다시 계산하지 않도록 하는 훅입니다. 주로 복잡한 계산을 캐시하고, 불필요한 계산을 피하는 데 유용합니다.

import { useMemo } from 'react';

const MyComponent = ({ items }) => {
  const sortedItems = useMemo(() => {
    console.log('리스트 정렬 중...');
    return items.sort();
  }, [items]);

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

위 코드에서 items가 변경되지 않으면 useMemo는 items.sort()를 다시 실행하지 않고, 이전에 계산된 결과를 반환합니다.

useCallback

useCallback은 함수를 메모이제이션하여, 의존성 배열이 변경되지 않으면 함수가 재생성되지 않도록 합니다. 주로 자식 컴포넌트에 전달되는 함수가 불필요하게 재생성되지 않도록 할 때 유용합니다.

import { useCallback } from 'react';

const ParentComponent = () => {
  const handleClick = useCallback(() => {
    console.log('버튼 클릭!');
  }, []); // 의존성 배열이 비어있으면, 한 번만 생성됨

  return <ChildComponent onClick={handleClick} />;
};

useCallback은 부모 컴포넌트가 재렌더링될 때 자식 컴포넌트에 전달되는 handleClick 함수가 재생성되지 않도록 보장합니다.

2.3. 불필요한 상태(state) 업데이트 방지

컴포넌트가 재렌더링되는 주요 원인 중 하나는 **상태(state)**가 변경되는 것입니다. 따라서 불필요한 상태 변경을 방지하는 것이 성능 최적화에 중요한 역할을 합니다. 예를 들어, 상태가 이미 변경되지 않았다면, 상태를 업데이트하지 않도록 조건문을 추가하는 방법이 있습니다.

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    if (count === 5) return; // 상태가 5일 때는 업데이트하지 않음
    setCount(count + 1);
  };

  return <button onClick={handleClick}>{count}</button>;
};

이 예시에서 setCount는 count 값이 5일 때 호출되지 않으며, 이를 통해 불필요한 상태 업데이트와 렌더링을 방지합니다.

2.4. shouldComponentUpdate와 PureComponent 활용

클래스형 컴포넌트에서는 shouldComponentUpdate 메서드를 사용하여 컴포넌트가 재렌더링될지 말지를 결정할 수 있습니다. PureComponent는 shouldComponentUpdate를 자동으로 구현한 컴포넌트로, 얕은 비교를 통해 상태나 props가 변경되지 않으면 재렌더링을 방지합니다.

class MyComponent extends React.PureComponent {
  render() {
    return <div>{this.props.value}</div>;
  }
}

함수형 컴포넌트에서는 React.memo가 이와 동일한 역할을 하므로, 함수형 컴포넌트에서 불필요한 렌더링을 막고 싶을 때는 React.memo를 사용하는 것이 좋습니다.

2.5. 코드 스플리팅(Code Splitting) 활용

코드 스플리팅은 애플리케이션의 초기 로딩 시간을 줄이고, 사용자가 실제로 필요한 코드만 로드할 수 있도록 도와주는 기법입니다. React에서는 React.lazy와 Suspense를 사용하여 컴포넌트를 동적으로 로딩할 수 있습니다.

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
};

React.lazy는 컴포넌트를 동적으로 로드하며, Suspense는 로딩 중에 표시할 컴포넌트를 지정합니다. 이렇게 하면 초기 페이지 로딩 시 불필요한 코드가 로드되지 않으므로 성능이 향상됩니다.

2.6. 이벤트 핸들러 최적화

이벤트 핸들러가 자주 변경되면 컴포넌트가 불필요하게 재렌더링될 수 있습니다. 이를 방지하려면, 이벤트 핸들러를 최적화하는 것이 중요합니다.

const MyComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // 의존성 배열에 count를 넣어 불필요한 렌더링 방지

  return <button onClick={handleClick}>{count}</button>;
};

위와 같이 useCallback을 사용하여 이벤트 핸들러가 필요할 때만 재생성되도록 최적화할 수 있습니다.

3. 결론

React에서 함수형 컴포넌트의 성능을 최적화하는 것은 단순히 성능을 개선하는 것 이상의 의미를 가집니다. 불필요한 렌더링을 방지하고, 앱의 응답 속도를 개선하며, 더 나은 사용자 경험을 제공할 수 있습니다. 이 글에서 소개한 방법들—React.memo, useMemo, useCallback, PureComponent, 코드 스플리팅 등—을 적절하게 활용하면, React 애플리케이션의 성능을 크게 향상시킬 수 있습니다.

성능 최적화는 일회성이 아니라, 애플리케이션의 크기나 복잡도가 커질수록 지속적으로 신경 써야 할 부분입니다. 위에서 소개한 기법들을 적절히 결합하여, 효

율적이고 빠른 React 애플리케이션을 구축할 수 있습니다.

반응형