개발이야기

React 고급 기술 익히기 - 1편: React의 렌더링 최적화 이해하기

diarmay 2025. 1. 24. 11:23

안녕하세요! "React 고급 기술 익히기" 시리즈의 첫 번째 글에 오신 것을 환영합니다. 이 시리즈에서는 React를 더 깊이 이해하고 효율적으로 활용할 수 있는 다양한 고급 기술과 패턴을 다뤄보겠습니다. 오늘은 React 개발의 핵심 중 하나인 렌더링 최적화에 대해 살펴보겠습니다.


왜 렌더링 최적화가 중요할까?

React는 컴포넌트 기반으로 동작하며, 상태(state)나 props가 변경될 때 컴포넌트를 다시 렌더링합니다. 하지만 모든 컴포넌트가 항상 다시 렌더링될 필요는 없습니다. 불필요한 렌더링은 성능 저하를 유발할 수 있기 때문에 이를 방지하는 최적화 전략이 중요합니다.


1. React의 렌더링 원리

React에서 컴포넌트는 다음과 같은 경우에 렌더링됩니다:

  1. 상태(state)가 변경될 때
  2. 부모 컴포넌트의 props가 변경될 때
  3. 부모 컴포넌트가 다시 렌더링될 때

React는 DOM 업데이트를 최소화하기 위해 Virtual DOM을 사용하지만, Virtual DOM 비교 작업(diffing)에도 비용이 듭니다. 따라서 불필요한 렌더링을 줄이는 것이 React 최적화의 핵심입니다.


2. 렌더링 최적화 전략

(1) React.memo

React.memo는 컴포넌트를 **메모이제이션(memoization)**하여 props가 변경되지 않으면 다시 렌더링하지 않도록 합니다.
이는 함수형 컴포넌트에서만 사용할 수 있습니다.

예제:

import React from 'react';

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

export default function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <ChildComponent value="React.memo 활용" />
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
    </div>
  );
}​

출력 결과:

  • 버튼을 클릭해도 ChildComponent는 다시 렌더링되지 않습니다. (props가 변경되지 않으므로)

(2) useCallback

React는 함수형 컴포넌트가 렌더링될 때마다 내부에 선언된 함수를 새로 생성합니다. 이를 방지하기 위해 **useCallback**으로 함수를 메모이제이션할 수 있습니다.

예제:

import React, { useState, useCallback } from 'react';

const Button = React.memo(({ onClick }) => {
  console.log('Button 렌더링');
  return <button onClick={onClick}>Click Me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('Button Clicked');
  }, []);

  return (
    <div>
      <Button onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
    </div>
  );
}
 

출력 결과:

  • handleClick 함수가 메모이제이션되어 Button 컴포넌트는 불필요한 재렌더링을 방지합니다.

(3) useMemo

useMemo는 값이 자주 변경되지 않는 계산 작업을 메모이제이션하여 성능을 최적화합니다.

예제:

import React, { useState, useMemo } from 'react';

export default function App() {
  const [count, setCount] = useState(0);

  const expensiveCalculation = (num) => {
    console.log('Expensive Calculation');
    return num * 2;
  };

  const result = useMemo(() => expensiveCalculation(count), [count]);

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
    </div>
  );
}

출력 결과:

  • count가 변경될 때만 expensiveCalculation 함수가 호출됩니다.

(4) React Profiler로 성능 분석

React Developer Tools에 포함된 Profiler를 사용하면 컴포넌트가 얼마나 자주 렌더링되는지, 어떤 이유로 렌더링되었는지를 확인할 수 있습니다.

Profiler 활성화 방법:

  1. React Developer Tools 설치.
  2. Chrome 개발자 도구에서 Profiler 탭 선택.
  3. 앱 상호작용 후 기록(Record) 버튼으로 렌더링 분석.

(5) 적절한 상태 관리

  • 상태를 최소화: 필요 이상으로 많은 상태를 관리하지 않도록 설계합니다.
  • 상태를 올바르게 분리: 상태를 관련 있는 컴포넌트끼리만 공유하도록 관리합니다.
  • Context API 사용 주의: Context의 값이 변경되면 해당 Context를 사용하는 모든 컴포넌트가 렌더링되므로, 적절히 분리하는 것이 중요합니다.

(6) Code Splitting과 Lazy Loading

  • Code Splitting: 필요한 부분의 코드만 로드하여 초기 로드 시간을 줄입니다.
  • Lazy Loading: React의 React.lazy를 사용해 컴포넌트를 동적으로 로드합니다.

예제:

import React, { Suspense } from 'react';

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

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

3. 렌더링 최적화 체크리스트

  1. React.memo를 활용해 불필요한 렌더링 방지.
  2. 함수나 계산 값은 useCallbackuseMemo로 메모이제이션.
  3. React Profiler로 성능 병목 현상 분석.
  4. Context API 사용 시 주의하여 리렌더링 최소화.
  5. Code Splitting과 Lazy Loading으로 초기 로드 최적화.

결론

React의 렌더링 최적화는 성능 개선과 사용자 경험 향상을 위해 꼭 필요한 과정입니다. React.memo, useCallback, useMemo와 같은 기술은 간단해 보이지만, 복잡한 애플리케이션에서 큰 차이를 만들어낼 수 있습니다.

다음 편에서는 React에서 상태 관리를 최적화하는 방법상태 관리 라이브러리 비교를 다룰 예정입니다. 


 

728x90
반응형