React.js 에러 경계(Error Boundaries)로 안정적인 앱 만들기

작성일 :

React.js 에러 경계(Error Boundaries)로 안정적인 앱 만들기

에러 경계란 무엇인가

React.js 애플리케이션을 개발할 때, 다양한 이유로 예기치 못한 에러가 발생할 수 있습니다. 이런 에러는 사용자 경험을 해칠 수 있으며, 전체 애플리케이션의 작동을 방해할 수도 있습니다. 에러 경계(Error Boundaries)는 이러한 문제를 해결하는 중요한 기술입니다. 에러 경계는 컴포넌트 트리에서 자신의 하위 트리에서 발생한 자바스크립트 에러를 잡아내고, 깨진 UI 대신 폴백 UI를 보여주는 React 컴포넌트를 뜻합니다.

간단히 말해, 에러 경계는 오류를 감지하고 적절한 대응책을 제공하여 애플리케이션의 안정성을 높이는 역할을 합니다.

에러 경계의 동작 원리

에러 경계는 componentDidCatch(error, errorInfo)static getDerivedStateFromError(error) 라이프사이클 메서드를 사용하여 동작합니다. 이 메서드들은 에러가 발생했을 때 호출되어 에러와 관련된 정보를 수집하고, 애플리케이션이 적절히 대응할 수 있도록 돕습니다.

  • componentDidCatch: 이 메서드는 컴포넌트 내에서 발생한 에러를 잡아내고, 이를 로깅하거나 특정 동작을 실행하는 데 사용됩니다.
  • static getDerivedStateFromError: 이 메서드는 에러가 발생하면 컴포넌트의 상태를 업데이트하여 폴백 UI를 렌더링하는 데 사용됩니다.

에러 경계의 구현

에러 경계를 실제로 구현하는 방법을 단계별로 살펴보겠습니다.

1. 기본적인 에러 경계 컴포넌트 작성

가장 먼저, 에러 경계를 처리할 기본적인 컴포넌트를 작성해 보겠습니다.

javascript
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('Error caught by ErrorBoundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 폴백 UI를 렌더링합니다.
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

export default ErrorBoundary;

2. 에러 경계 컴포넌트 적용

작성한 에러 경계 컴포넌트를 애플리케이션의 특정 영역에 적용하여 에러를 다룹니다. 예를 들어, 애플리케이션의 중요한 부분을 에러 경계로 감싸는 방법은 다음과 같습니다.

javascript
import React from 'react';
import ReactDOM from 'react-dom';
import ErrorBoundary from './ErrorBoundary';
import MyComponent from './MyComponent';

function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

이렇게 하면 MyComponent에서 발생하는 모든 에러는 ErrorBoundary가 처리하며, 사용자에게 깨진 UI 대신 폴백 UI를 제공합니다.

에러 경계의 한계

에러 경계는 매우 유용하지만, 제한사항이 있습니다. 다음은 에러 경계가 처리하지 못하는 에러의 몇 가지 예입니다:

  1. 이벤트 핸들러 내부에서 발생한 에러
  2. 비동기 코드(예: setTimeout이나 requestAnimationFrame 콜백) 내부의 에러
  3. 서버사이드 렌더링 중 발생한 에러
  4. 에러 경계 자체에서 발생한 에러

이와 같은 경우 에러 경계 대신 다른 방법으로 예외를 처리해야 합니다.

고급 사용법과 응용

로깅 및 사용자 피드백

에러 경계를 통해 에러를 감지한 후, 이를 외부 서비스로 로깅하거나 사용자에게 피드백을 제공할 수 있습니다. 예를 들어, Sentry와 같은 에러 모니터링 서비스를 이용하여 에러 정보를 전송할 수 있습니다.

javascript
import * as Sentry from '@sentry/react';

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

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

  componentDidCatch(error, errorInfo) {
    Sentry.withScope((scope) => {
      scope.setExtras(errorInfo);
      Sentry.captureException(error);
    });
    console.error('Error caught by ErrorBoundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

조건부 에러 경계

에러 경계를 조건부로 적용하여 특정 조건에서만 에러를 처리하도록 할 수도 있습니다. 예를 들어, 운영 환경(Production)에서만 에러를 경계로 처리하고, 개발 환경에서는 디버그 용도로 에러를 노출할 수 있습니다.

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

  static getDerivedStateFromError(error) {
    if (process.env.NODE_ENV === 'production') {
      return { hasError: true };
    }
    return { hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    if (process.env.NODE_ENV === 'production') {
      console.error('Error caught by ErrorBoundary:', error, errorInfo);
    } else {
      throw error;
    }
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

에러 경계를 통해 React.js 애플리케이션의 안정성을 크게 향상시킬 수 있습니다. 에러 경계는 사용자 경험을 보호하고, 버그를 더욱 쉽게 발견하고 수정하는 데 도움을 줍니다. 에러 경계를 활용하면, 예기치 못한 오류 발생 시에도 앱이 여전히 안정적으로 작동할 수 있습니다.