티스토리 뷰

 

에러 경계의 도입과정

이전에는 컴포넌트 내부( UI의 일부분 )의 자바스크립트 에러가 React의 내부 상태를 훼손하고

전체 어플리케이션을 중단 시켜버리는 치명적인 오류가 있었습니다.

 

이를 해결하기 위해 React 16에서는 에러 경계(Error Boundary)라는 새로운 개념이 도입되었다고 합니다.

 

 

에러 경계 ErrorBooundary란

에러 경계로 감싸면 하위 컴포넌트들에서 발생하는 자바스크립트 에러를 기록하며

깨진 컴포넌트 트리 대신 fallback UI를 보여주는 React 컴포넌트입니다.

 

에러 경계 에러 잡아내기

다음과 같은 에러를 잡아냅니다.

- 렌더링 도중 생명 주기 메서드

- 자식의 전체 트리의 자바스크립트 에러

 

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

- 이벤트 핸들러

- 비동기적 코드 ( setTImeout 혹은 requestAnimationFrame 콜백 등등... )

- 서버 사이드 렌더링

- 자식에서가 아닌 에러 경계 자체에서 발생하는 에러

 

 

에러 경계 컴포넌트 만들기

클래스형 React 컴포넌트 내부에서

static getDerivedStateFromError() 와 componentDidCatch() 중

하나 또는 둘다를 정의하면 그 자체가 에러 경계 컴포넌트가 됩니다.

 

왜 class형 컴포넌트?

왜냐면 아래의 두 라이프 사이클이 아직 Hooks에서 구현되지 않았기 때문입니다.

 

static getDerivedStateFromError()

- 에러가 발생한 후에 fallback UI를 렌더링 하기 위해선 static getDerivedStateFromError() 사용.

- 하위 자손 컴포넌트에서 오류가 발생했을 때 render 단계에서 호출됨.

 

이 메서드 내에서 에러정보를 서버로 전송하지 않는 이유는

리엑트에서 데이터 변경에 의한 화면 업데이트는 렌더단계와 커밋단계를 거친다고 합니다.

비동기 렌더링 단계에서 실행을 멈췄다가 다시 실행하는 과정에서

같은 생명주기 메서드를 여러번 호출 할 수 있어 componentDidCatch와 같은 여러번 호출하지 않는

생명주기 메서드 내에서 실행하는것이 올바른 방법이라고 합니다.

 

componentDidCatch()

- 에러 정보를 기록하려면 componentDidCatch() 를 사용.

- 하위 자손 컴포넌트에서 오류가 발생했을 때 commit 단계에서 호출됨.

 

이 메서드 내에서 화면으로 에러정보를 나타내지 않는 이유는

렌더링 된 결과를 돔에 반영한 후에 호출이 되기 때문에도 문제가 있을 뿐더러

서버사이드 렌더링 시에는 에러가 발생해도 해당 메서드는

호출이 되지 않는 문제가 있기 때문이라고 합니다.

 

에러 경계 컴포넌트 코드는 다음과 같습니다.

import { Component } from "react";
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError() {
    // 하위 자손 컴포넌트에서 오류가 발생했을 때 render 단계에서 호출
    // 다음 렌더링에서 fallback UI가 보이도록 상태를 업데이트 합니다.
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // 하위의 자손 컴포넌트에서 오류가 발생했을 때 commit 단계에서 호출
    // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
    console.error("Uncaught error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 에러상태이면 fallback UI를 렌더링합니다.
      // 커스텀한 UI를 렌더링 할 수 있습니다.
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

그리고 사용하는 페이지로 가서 다음과 같이 사용합니다.

<프로필> 컴포넌트를 <ErrorBoundary> 컴포넌트로 감싸주고 프로필 컴포넌트에서

에러가 발생하면 ErrorBoundary 에서 적용한 Something went wrong. 글자를 볼 수 있습니다.

에러를 발생시키기 위해 Profile의 Api 요청 주소를 없는 주소로 요청하였습니다.

이후 렌더링과정에서 생기는 오류를  getDerivedStateFromError() 메서드가 알아서 모두 다 잡아줍니다.

// error boundary 컴포넌트 사용하는 페이지
import ErrorBoundary from "../../common/components/boundary/ErrorBoundary";

export default function ReactErrorBoundaryPage() {
  return (
    <>
      <ErrorBoundary>
        <Profile />
      </ErrorBoundary>
    </>
  );
}

 

 

Custom Fallback UI

저는 fallback ui를 에러 경계 컴포넌트에 props를 보냄으로써 커스텀해주고 싶습니다.

그래서 위의 코드를 다음과 같이 리팩토링 해주었습니다.

 

fallback props를 받는다면 받은 fallback component를 렌더링, initialErrorFallBackUi를 통해 fallback props를 보내지 않는다면 기본적인 에러 ui를 렌더링 하도록 하여 fallback props를 보내지 않았더라도 에러가 나지 않도록 하였습니다.

* 아래의 코드는 typescript일때에는 defaultProps를 설정해줘야 합니다. 설정하지 않으면 fallback을 props로 넘기지 않았을때 ErrorBoundary 자체가 에러라고 표시됩니다. 이 내용은 추후 업데이트하도록 하겠습니다.

import { Component } from "react";
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
    this.initialErrorFallBackUi = <h1>Something went wrong.</h1>;
  }
  static getDerivedStateFromError() {
    // 하위 자손 컴포넌트에서 오류가 발생했을 때 render 단계에서 호출
    // 다음 렌더링에서 fallback UI가 보이도록 상태를 업데이트 합니다.
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // 하위의 자손 컴포넌트에서 오류가 발생했을 때 commit 단계에서 호출
    // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
    console.error("Uncaught error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 에러상태이면 fallback UI를 렌더링합니다.
      // 커스텀한 UI를 렌더링 할 수 있습니다.
      if (this.props.fallback) {
        // props로 fallback에 react 컴포넌트를 넣는다면 전달받은 fallback ui를 렌더링 해줍니다.
        return this.props.fallback;
      }
      return this.initialErrorFallBackUi;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

 

그리고 아래와 같이 ErrorBoundary 컴포넌트에 fallback props로 컴포넌트를 보낸다면

원하는 에러 fallback ui를 넣어줄 수 있습니다.

export default function ReactErrorBoundaryPage() {
  return (
    <>
      <ErrorBoundary fallback={<h1>에러경계 fallback ui 커스텀 테스트</h1>}>
        <Profile />
      </ErrorBoundary>
    </>
  );
}

 

 

지금까지 ErrorBoundary 컴포넌트를 만들고 사용하는 방법에 대해서 알아보았습니다.

추가로 아까 포착하지 않는 에러중 이벤트 핸들러의 에러를 감지하고 fallback ui를 렌더링 하기 위해서는

해당 컴포넌트 내의 핸들러 함수에 자바스크립트 try / catch 구문을 사용해 처리를 해야한다고합니다.

 

 

 

참고​

 

React 에러경계 문서

https://ko.reactjs.org/docs/error-boundaries.html

 

에러 경계(Error Boundaries) – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

댓글
최근에 올라온 글