[React] Error Boundary

hyeondoonge·2023년 10월 13일
0

개요

기본적으로 React에서 에러 핸들링, 로딩 핸들링 시 상태를 관리하고 상태에 따라 UI를 렌더링하도록 로직을 작성하게된다.

이에 관련된 상태들이 많아질수록 정상적인 코드를 이해하는 것도 힘든데, 에러 로딩이 복잡하게 섞여 코드의 유지보수성이 저하될 것이다.

이러한 문제를 React에서의 Error boundary와 Suspense를 이용해 개선할 수 있다. 이 컴포넌트들을 통해 달성할 수 있는 건 에러와 로딩 처리를 조상 컴포넌트에게 위임하는 것이다. 선언적인 구현도 함께 달성된다.

이 중에서도 Error Boundary를 학습해보자.

Error Boundary?

  • 렌더링 도중 에러 발생 시, 화면이 깨져보이게되는데 이러한 문제를 해결할 수 있는 기술
  • 조상에게 에러 처리 위임 및 선언적인 사용
  • 앱이 깨졌을 경우 보여줄 UI를 선언

사용법

크게 2단계 작업을 통해 화면에 Error를 그려낼 준비를 할 수 있다.

1. ErrorBoundary 컴포넌트 정의하기

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false }; // 에러 상태 지정
  }

  static getDerivedStateFromError(error) {
	// 에러 발생 시, 상태를 변경하여 이에 따른 fallback UI를 렌더링한다
    return { hasError: true };
  }

  componentDidCatch(error, info) {
	// optinal한 구현사항이다, 에러 발생 시 원하는 작업을 작성하면된다
	// 에러 로그 정보 출력 예시
    
    // Example "componentStack":
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logErrorToMyService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback;
    }

    return this.props.children;
  }
}

2. ErrorBoundary로 에러 핸들링을 원하는 컴포넌트를 감싸기

ErrorBoundary의 fallback속성은 위에서 정의한 속성으로, 에러 발생시 보여줄 컴포넌트를 전달한다.

MyPage는 임의의 컴포넌트로, 선언한 ErrorBoundary가 화면에 렌더링되는 모습을 보기위해서는 에러를 throw하는 컴포넌트도 준비되야한다.

<ErrorBoundary fallback={<p>somthing wrong</p>}>
   <MyPage />
</ErrorBoundary />

동작방식

  • getDerivedStateFromError(error)

    • React 단에서, 자식 컴포넌트가 렌더링 도중에 에러를 던지는 시점에 해당 함수가 호출됨
    • 파라미터인 error는 던져진 error이고, 이로부터 error 정보를 얻을 수 있음
    • error를 그려내고자 선언한 state를 반환 ⇒ 상태에 따라 정상적인 로직 또는 에러를 렌더링할지 결정하게됨
  • componentDidCatch(error, info)

    • optional한 구현사항
    • side effect 수행 (필요하다면 여기서 외부함수를 호출하여 자세한 에러 로그를 남길 수 있음!)
    • 파라미터 error는 위 함수와 동일한 값이며, info는 에러에 대한 추가적인 정보를 담고있는 객체
  • ErrorBoundary의 후손 컴포넌트가 에러를 던지면 그것을 잡아 getDerivedStateFromError가 실행된다. 그리고 render함수가 실행되는데, getDerivedStateFromError반환된 상태에 따라 fallback UI를 렌더링한다.

활용사례

  • 에러 렌더링 페이지 단위 뿐만 아니라 페이지를 구성하는 컴포넌트 기준으로, 에러를 부분적으로 렌더링할 수 있다. Local, Global한 ErrorBoundary가를 두어 에러를 다양한 방식으로 핸들링하여, 상황에 적합한 화면을 보여줄 수 있다.
  • ErrorBoundary가 중첩되어있는 상황에서, 하위 ErrorBoundary가 처리하지 못하는 ErrorBoundary를 상위 ErrorBoundary로 전파한다.
  • 네트워크 또는 서버 이상으로 인해 일시적으로 데이터를 받아오지 못하는 경우, 다시 요청 기능을 제공한다.
    음... 그냥 새로고침하면 안되나요? 데이터를 다시 불러오려면 사용자가 새로고침 버튼을 누르거나 key 입력으로 발생시켜야한다. 또한 이때 앱이 다시 다운로드 받아와지기 때문에 비용이 비교적 더 발생하는 단점을 가진다. 추가적인 요구사항이 필요하더라도 UX 향상할 수 있다면 시도해볼만한 기능이라고 생각한다. 뿐만아니라 구현도 간단하다! 아래는 직접 실험하고 테스트를 진행한 코드다. 상태에 refresh가 추가된 것 외에 큰 차이가 없어서, 설명은 더 하지 않겠다.
// typescript로 작성한 ErrorBoundary 활용 예시
import React, { ErrorInfo, ReactNode } from 'react';
import { MdRefresh } from 'react-icons/md';

interface IProps {
  fallback: ReactNode;
  children?: ReactNode;
}

interface IState {
  refresh: boolean;
  hasError: boolean;
}

export default class ErrorBoundary extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      refresh: false,
      hasError: false
    }; // 에러 상태 지정
  }

  static getDerivedStateFromError(error: unknown) {
    // 에러 발생 시, 상태를 변경하여 이에 따른 fallback UI를 렌더링한다
    // error는 컴포넌트에서 던진 에러
    return { hasError: true, refresh: false };
  }

  render() {
    const { fallback, children } = this.props;

    if (this.state.refresh) {
      return children;
    }

    if (this.state.hasError) {
      return (
        <div>
          {fallback}
          다시 시도할까요?
          <div onClick={() => this.setState({ hasError: false, refresh: true })}>
            <MdRefresh />
          </div>
        </div>
      );
    }

    return children;
  }
}

결론

서비스가 더 커질수록 관리해야하는 상태는 더 많아지게되며, 에러핸들링의 복잡도가 높아질텐데 이에따라 에러 핸들링 관심사 분리, 선언적 사용을 통해 코드 품질을 향상하는 것이 복잡도를 줄이는데 도움이 될 것 같다고 생각했다. 또한 try-catch와 처리되는 꼴이 비슷하기 때문에 이를 이해하고있다면 동작하는 방식을 쉽게 이해할 수 있을 것이다!

참고

Catching rendering errors with an error boundary - react
React의 Error Boundary를 이용하여 효과적으로 에러 처리하기 - kakaoent FE 기술 블로그

0개의 댓글