React, ErrorBoundary 사용해서 에러 처리하는 방법

    반응형

    React ErrorBoundary

     

    공부를 시작하면서 여러 강의들을 혼자 들으며 공부를 시작했던 나에겐 에러 처리가 사소했다. console.log(error) 혹은 setError(error)와 같은 사용자에게 피드백을 줄 수 없고 기본적인 로그 처리 정도로만 그쳤던 에러 핸들링이었는데 ErrorBoundary를 공부하면서 좀 더 나은 방법으로 에러를 처리할 수 있다는 것을 알게 되었고 공부했던 내용을 다시 한번 정리하려고 한다.

     

    ErrorBoundary


    ErrorBoundary는 React16에서 도입된 기능이다. React로 개발하다가 에러가 발생했을 때 적절한 에러 핸들링을 하지 않은 상태에서 보면 화면에 아무것도 보이지 않게 되었던 경험을 종종 한 적이 있다. React는 렌더링 중에 에러가 발생하면 전체 UI를 제거한다. 이는 불완전한 데이터는 잘못된 정보를 표시하거나 예기치 않은 동작이 발생할 수 있기 때문에 일어나는 조치인데 이러한 JavaScript 에러들을 잡아내고 에러가 발생했다면 fallback UI를 보여주는 리액트 컴포넌트이다.

     

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        return { hasError: true };
      }
    
      componentDidCatch(error, errorInfo) {
        console.log('에러 발생:', error, errorInfo);
      }
    
      render() {
        if (this.state.hasError) {
          return this.props.fallback;
        }
    
        return this.props.children;
      }
    }

     

    <ErrorBoundary fallback={<ErrorMessage />}>
      <App />
    </ErrorBoundary>

     

    클래스형 ErrorBoundary 컴포넌트의 기본적인 형태이다. hasError 라는 상태를 가지고 있는 ErrorBoundary의 자식 컴포넌트 트리에서 에러가 발생하면 static getDerivedStateFromError(error)에서 발생한 에러를 받아 ErrorBoundary의 hasError 상태를 업데이트 해서 에러가 발생했다는 것을 알린다. 

     

    componentDidCatch(error, errorInfo)는 발생한 에러정보를 수집하고 추가적인 상태를 업데이트 하거나, 사이드 이펙트를 처리하는데 사용되어진다. 이러한 ErrorBoundary 컴포넌트는 클래스형으로만 구현할 수 있지만, 함수형 컴포넌트에서도 사용이 가능하다. 

     

    ErrorBoundary 컴포넌트가 랩핑하는 부분의 UI를 fallback UI로 대체한다. 위의 예시에서는 <App /> 컴포넌트 전체를 랩핑하고 있기 때문에 에러가 발생하면 <App /> 전체를 fallback UI로 대체하지만 특정한 부분만 에러가 발생했을 때 fallback UI를 보이고 싶다면 그 부분만 랩핑해주면 된다.

     

    ErrorBoundary가 처리할 수 없는 에러


    fallback UI를 통해 사용자 경험을 향상시키는 ErrorBoundary 컴포넌트도 처리할 수 없는 에러가 있다. 여러가지가 있는데 이러한 에러들은 아래와 같다.

     

    1. 이벤트 핸들러 내부의 에러

    onClick, onChange와 같은 이벤트 핸들러에서 발생하는 에러는 렌더링 과정 외부에서 발생하기 때문에 ErrorBoundary에 에러가 잡히지 않는다.

     

    2. 비동기 코드에서의 에러

    ErrorBoundary는 렌더링 혹은 생명주기 메서드(componentDidMount, componentDidUpdate 등)가 실행되는 도중 발생하는 에러를 잡도록 설계되었는데 비동기 코드는 React의 렌더링 과정 이후 혹은 별도로 처리된다. 그렇기 때문에 에러가 잡히지 않고 주로 별도로 에러를 처리하는 로직을 통해 처리해야 한다.

     

    3. 서버 사이드 렌더링에서의 에러

    ErrorBoundary의 동작을 보면 static getDerivedStateFromError(error)에서 hasError 라는 에러 상태를 변경하고 render메서드를 통해 상태변화에 따른 fallback UI를 리턴한다. 이러한 상태변화에 따른 UI의 변경을 동적으로 제어하는 것인데 서버 사이드 렌더링을 보면 서버에서 HTML을 생성해 클라이언트로 전송한다. 생명주기에서 발생하는 에러를 잡아내는 ErrorBoundary는 이미 서버에서 생명주기 메서드가 끝난 상태이고, 이러한 과정 중에 에러가 발생하면 서버는 HTML 생성을 중단한다. 또한 상태변화에 따라 동적으로 UI를 업데이트 해야 하는데 서버 사이드 렌더링은 서버에서 동적으로 업데이트하지 못하고 새로운 HTML을 만들어 클라이언트에게 보내야 하는 문제가 있다. 그렇기 때문에 서버 사이드 렌더링에서 ErrorBoundary는 에러를 잡지 못한다.

     

    4. ErrorBoundary 자체의 에러

    ErrorBoundary 자체에서 발생하는 에러는 스스로 에러를 잡지 못한다. 그렇기 때문에 중첩된 ErrorBoundary를 사용하거나, 더 상위 버전의 ErrorBoundary를 사용해서 에러를 잡아야 한다. 쉽게 말하면 하위에 있는 ErrorBoundary가 에러를 처리하지 못한다면 중첩한 혹은 더 상위 버전의 ErrorBoundary에게 throw new Error를 통해 에러를 전파하여야 한다. 이렇게 처리함으로써 각 수준에 맞는 섬세한 에러 처리가 가능하고 애플리케이션의 안정성이 향상된다.

     

    try/catch 와 ErrorBoundary 의 비교


    try/catch 와 ErrorBoundary는 모두 에러를 처리하기 위한 방법이지만 각각의 특징과 차이점이 있다. 주요 특징과 차이점을 살펴보면 아래의 표와 같다.

     

      try/catch ErrorBoundary
    동작 방식 명령형 방식 선언적 방식
    에러 캐치 시점 error 발생 후 catch 블록에서 캐치 렌더링 중 발생하는 에러 캐치 
    적용 범위 특정 코드 블록 컴포넌트 트리 전체
    에러 처리 종류 모든 종류의 에러 처리 가능 렌더링 과정의 에러만 처리 가능
    대처 에러 발생 후 대체 로직 실행 fallback UI 렌더링

     

    살펴보면 try/catch 와 ErrorBoundary는 서로 상호 보완적인 느낌이 있다. 세부적인 에러 처리에는 try/catch를 UI의 안정성을 위한 에러 처리에는 ErrorBoundary를 사용하는 방식으로 두 에러 처리 방식을 적절히 조합해서 사용하는 것이 좋아 보인다.

     

     

    * 공부한 내용을 정리하는 것이기 때문에 틀린 부분이 있을 수 있습니다. 지적해 주시면 확인 후 수정하겠습니다.

     

     

    반응형

    댓글