ERROR BOUNDARY 다루기(ErrorBoundary + react-error-boundary)

송우든·2023년 12월 6일
1

Dev

목록 보기
17/20
post-thumbnail

오늘은 React16에서 도입된 ErrorBoundary에 대해 정리해보려고 한다. 대부분 내용은 리액트 공식문서를 참고하여 작성하였고, react-dev에서도 도움을 받았다.

Error Boundary(에러 경계)

이는 에러 처리를 더 쉽고 효과적으로 할 수 있게 도와준다. ErrorBoundary는 하위 컴포넌트 트리에서 발생한 에러를 잡아 선언적으로 처리할 수 있는 컴포넌트를 말한다. (즉, 하위 컴포넌트 어디에서든 오류를 기록하며, 깨진 컴포넌트 대신 Fallback UI를 보여준다.)

++ fallback UI는 보조적인 사용자 인터페이스로 의미하면 된다. 특정 이유로 주요 사용자 인터페이스가 실패하거나 보이지 않는 경우 대체 기능을 가진 UI로 사용된다.

ErrorBoundary는 크게 두 가지 메서드를 정의하고 있다. 이 두가지 중 하나만 정의 하여도 컴포넌트 자체에 에러 경계가 된다. static getDerivedStateFromError()는 오류에 대한 응답으로 상태를 업데이트하고 사용자에게 오류 메세지를 표시할 수 있는 기능과 함께 사용된다. (다음 렌더링에서 fallback UI가 보이도록 한다.)

componentDidCatch()는 서비스에 대한 오류를 기록한다.

이러한 ErrorBoundary는 자바스크립트에서 catch {}문과 유사하게 동작하지만 컴포넌트에만 적용된다는 차이가 있다. (기존에 처리해왔던 try-catch문은 try로 감싼 곳의 에러를 핸들링했다면 ErrorBoundary는 선언적으로 핸들링할 수 있다.)

ErrorBoundary는 아래에 경우에는 에러를 핸들링하지 않는다.

  • 이벤트 핸들러
  • 비동기적 코드(setTimeout이나 requestAnimationFrame 등)
  • 서버 사이드 렌더링
  • ErrorBoundary 컴포넌트 자체에서 발생하는 에러

오늘은 두 가지 방법으로 ErrorBoundary를 다루어 보려고 한다.

1. Error Boundary 클래스 생성하여 처리하기

현재 나는 VITE + REACT + TYPESCRIPT에서 프로젝트를 진행하고 있다. 먼저, 나는 src/compoents/ErrorBoundary안에 파일을 생성해두었다.

import React from "react";
/**
 * ErrorBoundary 컴포넌트의 프로퍼티를 정의합니다.
 * @interface ErrorBoundaryProps
 */
interface ErrorBoundaryProps {
  fallback: JSX.Element;
  children: JSX.Element;
}

/**
 * ErrorBoundary의 state 프로퍼티를 정의합니다.
 * @interface ErrorBoundaryState
 */
interface ErrorBoundaryState {
  hasError: boolean;
}

/**
 * 에러를 처리하고 대체 UI를 표시하는 React 컴포넌트입니다.
 * @class ErrorBoundary
 * @extends {React.Component<ErrorBoundaryProps, ErrorBoundaryState>}
 */
class ErrorBoundary extends React.Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
    console.error("Error caught by componentDidCatch:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

이렇게 만들어진 ErrorBoundary 컴포넌트를 적용해보자. 나는 임시적으로 App 컴포넌트를 감싸게 설정하였다.

ReactDOM.createRoot(document.getElementById("root")!).render(
  <ErrorBoundary fallback={<h1>잘못된 접근입니다:(</h1>}>
    <App />
  </ErrorBoundary>
);

그리고 App컴포넌트에서 clientKey의 값의 존재 여부에 따라 Error를 핸들링하게 구현해보자. 아래와 같이 코드를 실행해보면 예상했던 화면을 보여줄 것이다.

const App = () => {
  const clientKey = "sfaa-2agb-324d-afdv";
  if (!clientKey) throw Error("clientKey is undefined");

  return (
    <div>
      <h1>Error Boundary Test</h1>
      <label> clientKey : {clientKey} </label>
    </div>
  );
};

export default App;

반대로 아래와 같이 코드를 작성해보자.

const App = () => {
  let clientKey = undefined;
  if (!clientKey) throw Error("clientKey is undefined");

  return (
    <div>
      <h1>Error Boundary Test</h1>
      <label> clientKey : {clientKey} </label>
    </div>
  );
};
export default App;

이렇게 위와 같이 상황에 따라서 ApiErrorBoundary나 전역에서 관리되어야 하는 케이스를 담을 GlobalErrorBoundary등을 생성하여 에러를 처리할 수 있다.


2. react-error-boundary 라이브러리 사용하여 처리하기

위에서 다룬 ErrorBoundary는 클래스형으로 구현이 되어있다. 함수형으로 만들기에는 두 메서드가 존재하지 않기 때문에 공식문서에서도 클래스형만 다루고 있다. 구글링을 해보면서 함수형 컴포넌트에서도 이를 처리할 수 있다는 사실을 발견했다. (링크-함수형으로 ErrorBoundary)

하지만 오늘은 더 간단하게 구현 가능한 react-error-boundary를 이용해보려고 한다.

react-error-boundaryErrorBoundary를 확장하여 개선된 기능을 제공하는 라이브러리로, 전역 에러 핸들링 및 커스텀 에러 UI를 간편하게 구현할 수 있게 해준다. 어느 위치에서든 에러를 감지하고 사용자에게 보여줄 allback UI를 쉽게 지정하여 처리할 수 있다.

지금부터 하나씩 진행해보도록 하자. 먼저, 설치를 진행한다.(나는 npm으로 패키지 매니저를 선택했다.)

npm install --save-dev react-error-boundary

설치된 라이브러리를 통해서 ErrorBoundary를 만들어보자. 기본적으로 아래와 같이 사용할 수 있다.

<ErrorBoundary fallbackComponent={ErrorFallback}>
	<MyComponent/>
</ErrorBoundary>

예시를 들어 위와 같이 App 컴포넌트를 수정해보겠다.

import { ErrorBoundary, FallbackProps } from "react-error-boundary";
const App = () => {
  return (
    <div>
      <h1>react-error-boundary TEST</h1>
      <ErrorBoundary FallbackComponent={FallbackComponent}>
        <ThrowErrorComponent name="송우든" />
      </ErrorBoundary>
    </div>
  );
};

/**
 * ErrorBoundary에 잡혔을 경우, 보여줄 Fallback 컴포넌트
 * @param {FallbackProps} props error, resetErrorBoundary
 * @returns {JSX.Element} Fallback UI
 */
const FallbackComponent = ({ error, resetErrorBoundary }: FallbackProps) => {
  return (
    <div>
      <h2>잘못된 접근 방식</h2>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>다시 시도</button>
    </div>
  );
};

/**
 * Error가 발생할 수 있는 컴포넌트
 * @param {{[key:string] : string}} props name
 * @returns {JSX.Element} Error가 없을 때 보여질 UI
 */
const ThrowErrorComponent = ({ name }: { [key: string]: string }) => {
  if (name === "") throw Error("[ThrowErrorComponent] : Invalid name");
  return <h1>Hello, {name}!</h1>;
};
export default App;

name에 값이 빈 문자열이라면 FallbackComponent컴포넌트가 다음과 같이 나타난다.

반대로 name이 빈 문자열이 아니라면 올바르게 동작하는 것을 확인할 수 있다.

그리고 마지막으로 react-error-boundary에는 useErrorBoundary라는 훅으로 제공하는데 이 훅을 이용해 함수형 컴포넌트 내부에서 에러를 핸들링할 수 있다. 아래와 같이 비동기 처리를 진행해야할 때, 에러 핸들링이 필수적으로 필요하다.

import React from 'react';
import { useErrorBoundary } from "react-error-boundary";

const MyComponent = () => {
  const { showBoundary } = useErrorBoundary();

  const fetchData = async () => {
    try {
      const result = await fetch("https://example.com/data");
      const data = await result.json();
      console.log(data);
    } catch (error) {
      showBoundary(error);
    }
  };

  return (
    <div>
      <button onClick={fetchData}>데이터 가져오기</button>
    </div>
  );
};

export default MyComponent;

useErrorBoundary훅을 통해서 handleError함수를 얻어 catch블록 내에서 발생한 에러를 ErrorBoundary에 전달하여 Fallback UI를 활성화시킬 수 있다.

마무리

요즘 이것저것 다양한 분야를 공부해 보려고 한다. 그 동안 몰랐던 프론트엔드 관린 지식이나 기술은 물론 IT라는 분야의 관심을 가지고 학습을 진행하고 있다. 사실 이번 소프트웨어 박람회를 다녀오며 많은 생각을 가지게 되었다. 특히, 보다 나은 사용자 경험과 안전한 서비스를 만들기 위해 개발자로서 어떤 노력을 할 수 있는지, 그 동안 어떤식으로 처리해 왔는지를 고민하게 됐다. 이번 기회에 에러 핸들링에 대해 정확하게 학습하고 적용해볼 수 있게 노력해야겠다.

profile
개발 기록💻

0개의 댓글