Error Boundary와 Fallback UI

Debug-Life ·2023년 3월 24일
0

리액트로 개발을 하다 에러가 생기면 해당 부분의 컴포넌트만 렌더링되지 않는것이 아니라 전부 다 렌더링이 되지 않는다. 다시말해 리액트에서는 예외 발생 시 예외를 발생한 컴포넌트만 언마운트 되지 않는다. 예외 발생 시 모든 컴포넌트가 언마운트 된다.

이는 잘못된 정보를 사용자에게 보여 주는 것보다는 아무것도 보여주지 않는 것이 낫기 때문이다.

하지만, 사용자가 보기에 아무것도 보여주지 않는 것은 잘못된 정보를 제공하지 않았을 뿐이지 나쁜 사용자 경험을 제공하게 된다. 차라리 어디가 에러가 나고 있는지 보여주는게 백번 낫다.
이러한 문제점을 해결하기 위해 등장한 것이 Error Boundary 이다


1. Error Boundary :

에러 경계는 하위 컴포넌트 트리의 어디에서든 자바스크립트 에러를 기록하며 깨진 컴포넌트 트리 대신 폴백 UI를 보여주는 React 컴포넌트입니다. 에러 경계는 렌더링 도중 생명주기 메서드 및 그 아래에 있는 전체 트리에서 에러를 잡아냅니다.


2. 예외사항

공식문서에 나온 예외사항들은 아래 4가지다.

Note

에러 경계는 다음과 같은 에러는 포착하지 않습니다.

  • 이벤트 핸들러
  • 비동기적 코드 (예: setTimeout 혹은 requestAnimationFrame 콜백)
  • 서버 사이드 렌더링
  • 자식에서가 아닌 에러 경계 자체에서 발생하는 에러

3. 사용방법

공식 문서 예제 코드
난 여기서 3가지만 커스텀했다

  • export 만 추가.
  • import React from "react" 추가
  • fallbackImage 와 text 추가.

✍ ErrorBoundary.jsx

import React from "react";
import fallbackImage from "../assets/fallbackImage.png";

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

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

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

  render() {
    if (this.state.hasError) {
      /// 사용자 지정 폴백 UI를 렌더링할 수 있습니다
      //   return <h1>Something went wrong.</h1>;
      return (
        <div
          style={{
            display: "flex-box",
            textAlign: "center",
            // justifyContent: "center",
          }}
        >
          <h1 style={{ display: "block" }}>무언가 오류가 있어요.</h1>
          <img src={fallbackImage} alt="fallback" />;
        </div>
      );
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

✍ main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

import ErrorBoundary from "./components/ErrorBoundary";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        path: "/",
        element: <Posts />,
        loader: postsLoader,
        children: [{ path: "/create-post", element: <NewPost /> }],
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <ErrorBoundary>
      <RouterProvider router={router} />
    </ErrorBoundary>
  </React.StrictMode>
);


  • 코드를 설명하자면, 에러의 상태를 state로 관리하고, 에러가 발생하면 이를 getDerivedStateFromError로 확인하여 에러의 상태를 바꿔 fallback UI를 보여주는 식이다.

  • 그리고 사용하고 싶은 부분에 import 한 다음에 감싸주면 된다. 난 그냥 스크립트를 따로 뺴서 가장 최상단의 라우터 루트 컴포넌트를 감싸주었다.



3-1. 에러발생

코드를 제대로 짰는데 자꾸만 fallback 이미지가 안나왔다. 이것도 찾는데 참 오래걸렸다. 다른 블로그에서도 다들 똑같은 말만 할 뿐 방법을 알려주지 않았다.


3-2. 해결방법

결론부터 이야기하면 문제는 main.jsx 에서 <RouterProvider router={router} /> 부분을 ErrorBoundary 로 감싼것이었다.

래핑할 부분을 RouterProvider 가 아니라 RootLayout 자식 라우트인 Posts 컴포넌트를 감싸면 해결된다. 즉 이렇게 하면 Posts의 아래로 렌더링 되는 곳에서부터 문제가 발생하면 내가 import 한 fallback UI가 나오게 된다.

3-3. RouterProvider 래핑하면 안되는 이유

솔직히 지금은 일단 여기까지 찾아보고 그냥 외운다. 전체를 래핑하지 않고, 특정 컴포넌트만 래핑해야한다는 것으로 외운다.

일반적으로 ErrorBoundary 구성 요소는 전체 응용 프로그램이 아닌 응용 프로그램의 특정 부분에 대한 오류를 처리하도록 설계되었기 때문에 전체 응용 프로그램을 ErrorBoundary로 래핑하는 것은 권장되지 않습니다.

전체 응용 프로그램을 ErrorBoundary로 래핑하면 ErrorBoundary 구성 요소 외부에서 발생하는 모든 오류가 제대로 포착 및 처리되지 않습니다. 이로 인해 예기치 않은 동작이 발생할 수 있으며 애플리케이션에서 문제를 디버깅하기 어려울 수 있습니다.

대신 구성 요소 계층의 적절한 수준에서 ErrorBoundary 구성 요소를 사용하여 응용 프로그램의 특정 부분 내에서 발생하는 오류를 처리하는 것이 좋습니다. 이를 통해 오류를 보다 효과적으로 격리하고 처리하는 동시에 사용자를 위한 대체 UI를 계속 제공할 수 있습니다.


✍ main.jsx (래핑 컴포넌트 수정 후)

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

import ErrorBoundary from "./components/ErrorBoundary";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        path: "/",
        element: (
          <ErrorBoundary>
            <Posts />
          </ErrorBoundary>
        ),
        loader: postsLoader,
        children: [{ path: "/create-post", element: <NewPost /> }],
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

결과화면


profile
인생도 디버깅이 될까요? 그럼요 제가 하고 있는걸요

0개의 댓글