Error boundary

우리가 React를 사용하는 이유는 상태의 변화로 컴포넌트를 제어하기 쉽기 때문입니다. 이러한 컴포넌트 하나하나가 모여 하나의 웹 사이트를 구성하게 됩니다.

그런데, 쌓아 올린 컴포넌트 중 어느 한 군데서 에러가 발생한다면?💥

React 또한 Javascript의 일부일 뿐.. 에러가 발생한 즉시 React는 모든컴포넌트를 언마운트 하며 죽어버릴 것입니다. 한곳에서 발생한 오류로 어플리케이션이 작동 중단돼서는 안 될 일입니다.

그렇다면 try/catch처럼 오류를 제어할 순 없을까? 이러한 개념에서 Error boundary가 지원되기 시작했습니다.

에러 경계(Error Boundaries)

Class 형식이 필요한 이유

그래서, Class 형식이 필요한 이유가 무엇인가에 대해 간단히 설명하자면, 한마디로 Hooks에서 지원하는 방식으로는 오류 발생 시 제어할 방법이 없어서 입니다.😨

  • getSnapshotBeforeUpdate: 가장 마지막으로 렌더링 된 결과가 DOM 등에 반영되었을 때 호출
  • getDerivedStateFromError: 하위의 자손 컴포넌트에서 오류가 발생했을 때 render 단계에서 호출
  • componentDidCatch: 하위의 자손 컴포넌트에서 오류가 발생했을 때 commit 단계에서 호출

render 단계는 React가 DOM 갱신이 일어날 때 이전과 이후를 비교하며 변경 사항을 계산하는 단계입니다.

commit 단계는 React가 비교를 끝내고 DOM에 직접적으로 갱신될 내용을 적용하는 단계입니다.

위의 세 가지 라이프 사이클이 아직은 Hooks에서 구현되지 않았기 때문입니다.

Do Hooks cover all use cases for classes?

Errorboundary 컴포넌트 구현

결국 하위의 자손 컴포넌트에서 오류가 발생했을 때 무언가 조치하려면 Errorboundary를 getDerivedStateFromError, componentDidCatch의 단계에서 처리해야 하므로 Class 형식으로 만들어야 제대로 된 라이프 사이클에서 처리할 수 있습니다.

import React from 'react';
import ErrorPage from '../../pages/ErrorPage';

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

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

  componentDidCatch(error, errorInfo) {
    logErrorToMyService(error, errorInfo);
    console.error('Uncaught error:', error, errorInfo);
  }

  render() {
    const { hasError } = this.state;
    if (hasError) {
      return <ErrorPage />;
    }

    const { children } = this.props;
    return children;
  }
}

export default ErrorBoundary;

getDerivedStateFromError 단계에서 에러를 확인, hasError의 상태를 true로 변경하고 ErrorPage를 렌더링 하도록 구성하였습니다. 만약 로깅 로직(Sentry 등)이 존재한다면 componentDidCatch 단계에서 로깅 서비스를 호출하면 됩니다.

Errorboundary는 try/catch처럼 트리 내에서 하위에 존재하는 컴포넌트의 에러만을 포착한다는 점을 잊지 말아야 합니다. 그러므로 최상위에 하나를 배치하여 모두 처리할지, 혹은 에러가 날 가능성이 있는 컴포넌트마다 따로 감싸줄지는 사용자가 선택하면 됩니다.

마치며

React는 Hooks가 Class를 대체할 수 있도록, 모든 라이프 사이클을 지원하는 것을 목표로 하고 있습니다. 위의 지원하지 않는 라이프 사이클도 언젠가는 추가할 예정이라고 하니 조금만 더 기다리면 될 것 같습니다.