한국어

복원력 있고 사용자 친화적인 애플리케이션 구축을 위해 React 오류 경계를 마스터하세요. 모범 사례, 구현 기술, 고급 오류 처리 전략을 배워보세요.

React 오류 경계: 견고한 애플리케이션을 위한 우아한 오류 처리 기법

역동적인 웹 개발 세계에서 견고하고 사용자 친화적인 애플리케이션을 만드는 것은 가장 중요합니다. 사용자 인터페이스 구축을 위한 인기 있는 자바스크립트 라이브러리인 React는 오류 경계(Error Boundaries)라는 오류를 우아하게 처리하는 강력한 메커니즘을 제공합니다. 이 종합 가이드에서는 오류 경계의 개념을 깊이 파고들어, 복원력 있는 React 애플리케이션 구축을 위한 목적, 구현 및 모범 사례를 탐구합니다.

오류 경계의 필요성 이해하기

React 컴포넌트는 다른 코드와 마찬가지로 오류에 취약합니다. 이러한 오류는 다음과 같은 다양한 원인에서 발생할 수 있습니다:

적절한 오류 처리 없이는 React 컴포넌트의 오류가 전체 애플리케이션을 중단시켜 좋지 않은 사용자 경험을 초래할 수 있습니다. 오류 경계는 이러한 오류를 포착하고 컴포넌트 트리 위로 전파되는 것을 방지하여, 개별 컴포넌트가 실패하더라도 애플리케이션이 계속 작동하도록 보장하는 방법을 제공합니다.

React 오류 경계란 무엇인가?

오류 경계는 자식 컴포넌트 트리 어디에서든 자바스크립트 오류를 포착하고, 해당 오류를 기록하며, 충돌한 컴포넌트 트리 대신 대체 UI를 표시하는 React 컴포넌트입니다. 이는 안전망 역할을 하여 오류가 전체 애플리케이션을 중단시키는 것을 방지합니다.

오류 경계의 주요 특징:

오류 경계 구현하기

기본적인 오류 경계 컴포넌트를 만드는 과정을 살펴보겠습니다:

1. 오류 경계 컴포넌트 만들기

먼저, ErrorBoundary와 같이 새로운 클래스 컴포넌트를 생성합니다:


import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }

  static getDerivedStateFromError(error) {
    // 다음 렌더링에서 대체 UI를 표시하도록 상태를 업데이트합니다.
    return {
      hasError: true
    };
  }

  componentDidCatch(error, errorInfo) {
    // 오류 보고 서비스에 오류를 기록할 수도 있습니다.
    console.error("Caught error: ", error, errorInfo);
    // 예시: logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 사용자 정의 대체 UI를 렌더링할 수 있습니다.
      return (
        <div>
          <h2>문제가 발생했습니다.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }

    return this.props.children; 
  }
}

export default ErrorBoundary;

설명:

2. 오류 경계 사용하기

오류 경계를 사용하려면 오류를 발생시킬 수 있는 모든 컴포넌트를 ErrorBoundary 컴포넌트로 감싸기만 하면 됩니다:


import ErrorBoundary from './ErrorBoundary';

function MyComponent() {
  // 이 컴포넌트는 오류를 발생시킬 수 있습니다.
  return (
    <ErrorBoundary>
      <PotentiallyBreakingComponent />
    </ErrorBoundary>
  );
}

export default MyComponent;

만약 PotentiallyBreakingComponent가 오류를 발생시키면, ErrorBoundary가 이를 포착하고 오류를 기록한 후 대체 UI를 렌더링합니다.

3. 전역 컨텍스트를 사용한 예시

원격 서버에서 가져온 상품 정보를 표시하는 전자상거래 애플리케이션을 생각해보세요. ProductDisplay라는 컴포넌트가 상품 상세 정보를 렌더링하는 역할을 합니다. 하지만 서버가 때때로 예상치 못한 데이터를 반환하여 렌더링 오류를 일으킬 수 있습니다.


// ProductDisplay.js
import React from 'react';

function ProductDisplay({ product }) {
  // product.price가 숫자가 아닐 경우 발생할 수 있는 오류를 시뮬레이션합니다.
  if (typeof product.price !== 'number') {
    throw new Error('Invalid product price');
  }

  return (
    <div>
      <h2>{product.name}</h2>
      <p>Price: {product.price}</p>
      <img src={product.imageUrl} alt={product.name} />
    </div>
  );
}

export default ProductDisplay;

이러한 오류로부터 보호하기 위해 ProductDisplay 컴포넌트를 ErrorBoundary로 감싸줍니다:


// App.js
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import ProductDisplay from './ProductDisplay';

function App() {
  const product = {
    name: 'Example Product',
    price: 'Not a Number', // 의도적으로 잘못된 데이터
    imageUrl: 'https://example.com/image.jpg'
  };

  return (
    <div>
      <ErrorBoundary>
        <ProductDisplay product={product} />
      </ErrorBoundary>
    </div>
  );
}

export default App;

이 시나리오에서는 product.price가 의도적으로 숫자가 아닌 문자열로 설정되었기 때문에 ProductDisplay 컴포넌트에서 오류가 발생합니다. ErrorBoundary는 이 오류를 포착하여 전체 애플리케이션의 중단을 막고, 깨진 ProductDisplay 컴포넌트 대신 대체 UI를 표시합니다.

4. 국제화된 애플리케이션에서의 오류 경계

글로벌 사용자를 위한 애플리케이션을 구축할 때, 더 나은 사용자 경험을 제공하기 위해 오류 메시지는 현지화되어야 합니다. 오류 경계는 국제화(i18n) 라이브러리와 함께 사용하여 번역된 오류 메시지를 표시할 수 있습니다.


// ErrorBoundary.js (i18n 지원 포함)
import React from 'react';
import { useTranslation } from 'react-i18next'; // react-i18next를 사용한다고 가정

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
    };
  }

  static getDerivedStateFromError(error) {
    return {
      hasError: true,
      error: error,
    };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Caught error: ", error, errorInfo);
    this.setState({errorInfo: errorInfo});
  }

  render() {
    if (this.state.hasError) {
      return (
        <FallbackUI error={this.state.error} errorInfo={this.state.errorInfo}/>
      );
    }

    return this.props.children;
  }
}

const FallbackUI = ({error, errorInfo}) => {
  const { t } = useTranslation();

  return (
    <div>
      <h2>{t('error.title')}</h2>
      <p>{t('error.message')}</p>
      <details style={{ whiteSpace: 'pre-wrap' }}>
        {error && error.toString()}<br />
        {errorInfo?.componentStack}
      </details>
    </div>
  );
}


export default ErrorBoundary;

이 예제에서는 react-i18next를 사용하여 대체 UI의 오류 제목과 메시지를 번역합니다. t('error.title')t('error.message') 함수는 사용자가 선택한 언어에 따라 적절한 번역을 가져옵니다.

5. 서버 사이드 렌더링(SSR) 고려사항

서버 사이드 렌더링 애플리케이션에서 오류 경계를 사용할 때, 서버가 충돌하는 것을 방지하기 위해 오류를 적절하게 처리하는 것이 중요합니다. React 문서에서는 서버에서의 렌더링 오류 복구를 위해 오류 경계를 사용하지 말 것을 권장합니다. 대신, 컴포넌트를 렌더링하기 전에 오류를 처리하거나 서버에서 정적 오류 페이지를 렌더링하세요.

오류 경계 사용을 위한 모범 사례

고급 오류 처리 전략

1. 재시도 메커니즘

경우에 따라, 오류를 유발한 작업을 재시도하여 오류로부터 복구할 수 있습니다. 예를 들어, 네트워크 요청이 실패하면 짧은 지연 후 재시도할 수 있습니다. 오류 경계는 재시도 메커니즘과 결합하여 더 복원력 있는 사용자 경험을 제공할 수 있습니다.


// ErrorBoundaryWithRetry.js
import React from 'react';

class ErrorBoundaryWithRetry extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      retryCount: 0,
    };
  }

  static getDerivedStateFromError(error) {
    return {
      hasError: true,
    };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Caught error: ", error, errorInfo);
  }

  handleRetry = () => {
    this.setState(prevState => ({
      hasError: false,
      retryCount: prevState.retryCount + 1,
    }), () => {
      // 이는 컴포넌트를 강제로 다시 렌더링합니다. 제어되는 props를 사용하는 더 나은 패턴을 고려하세요.
      this.forceUpdate(); // 경고: 주의해서 사용하세요
      if (this.props.onRetry) {
          this.props.onRetry();
      }
    });
  };

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>문제가 발생했습니다.</h2>
          <button onClick={this.handleRetry}>재시도</button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundaryWithRetry;

ErrorBoundaryWithRetry 컴포넌트는 클릭 시 hasError 상태를 재설정하고 자식 컴포넌트를 다시 렌더링하는 재시도 버튼을 포함합니다. 재시도 횟수를 제한하기 위해 retryCount를 추가할 수도 있습니다. 이 접근 방식은 일시적인 네트워크 중단과 같은 일시적인 오류를 처리하는 데 특히 유용할 수 있습니다. `onRetry` prop이 적절하게 처리되어 오류가 발생했을 수 있는 로직을 다시 가져오거나 재실행하도록 해야 합니다.

2. 기능 플래그

기능 플래그를 사용하면 새 코드를 배포하지 않고도 애플리케이션의 기능을 동적으로 활성화하거나 비활성화할 수 있습니다. 오류 경계는 기능 플래그와 함께 사용하여 오류 발생 시 기능을 점진적으로 저하시킬 수 있습니다. 예를 들어, 특정 기능이 오류를 일으키는 경우 기능 플래그를 사용하여 해당 기능을 비활성화하고 사용자에게 해당 기능이 일시적으로 사용할 수 없음을 알리는 메시지를 표시할 수 있습니다.

3. 서킷 브레이커 패턴

서킷 브레이커 패턴은 애플리케이션이 실패할 가능성이 높은 작업을 반복적으로 실행하는 것을 방지하기 위해 사용되는 소프트웨어 디자인 패턴입니다. 이는 작업의 성공 및 실패율을 모니터링하고, 실패율이 특정 임계값을 초과하면 '회로를 열어' 일정 기간 동안 해당 작업의 추가 실행을 방지하는 방식으로 작동합니다. 이는 연쇄적인 실패를 방지하고 애플리케이션의 전반적인 안정성을 향상시키는 데 도움이 될 수 있습니다.

오류 경계는 React 애플리케이션에서 서킷 브레이커 패턴을 구현하는 데 사용될 수 있습니다. 오류 경계가 오류를 포착하면 실패 카운터를 증가시킬 수 있습니다. 실패 카운터가 임계값을 초과하면 오류 경계는 사용자에게 해당 기능을 일시적으로 사용할 수 없음을 알리는 메시지를 표시하고 해당 작업의 추가 실행을 방지할 수 있습니다. 일정 시간이 지나면 오류 경계는 '회로를 닫고' 작업 실행을 다시 허용할 수 있습니다.

결론

React 오류 경계는 견고하고 사용자 친화적인 애플리케이션을 구축하는 데 필수적인 도구입니다. 오류 경계를 구현함으로써 오류가 전체 애플리케이션을 중단시키는 것을 방지하고, 사용자에게 우아한 대체 UI를 제공하며, 디버깅 및 분석을 위해 모니터링 서비스에 오류를 기록할 수 있습니다. 이 가이드에서 설명한 모범 사례와 고급 전략을 따르면, 예상치 못한 오류에 직면하더라도 복원력 있고 신뢰할 수 있으며 긍정적인 사용자 경험을 제공하는 React 애플리케이션을 구축할 수 있습니다. 글로벌 사용자를 위해 현지화된 유용한 오류 메시지를 제공하는 데 집중하는 것을 기억하세요.