Tech/ReactJS

에러 바운더리를 사용한 선언적 에러 핸들링

닝닝깅 2024. 5. 21. 10:46

📍 기존의 에러핸들링

매번 try-catch로 api 통신 에러만 잡아냈을 뿐 에러처리에 대한 진지한 고민을 해본 경험이 없었다. 에러의 종류를 알고 커스텀 에러를 정의해 전역적인 에러 처리를 하고 싶다는 생각이 들었다.

 

📍 에러 핸들링 방법 선택

에러 핸들링 방법은 여러가지가 있다

 

1. try-catch문

- 특정 동작이나 함수 호출에 대한 에러 처리

 

2. promise catch문

- 네트워크 요청 등의 비동기 코드에서 발생한 에러 처리

 

3. axios interceptor

- 네트워크 에러 처리

- axios 라이브러리 제공 기능

 

4. react-query onError

- 데이터 fetching 중 발생한 에러 처리

- react query 라이브러리 제공 기능

 

5. error boundary

- 하위 컴포넌트의 에러를 포착하고 폴백 UI 렌더링

 

이 중 이번 프로젝트에서는 에러 바운더리를 선택했고, 이유는 다음과 같다.

 

이유1) 선언적 에러 처리가 가능하다.

선언적 에러 처리는 에러가 발생할 경우 어떻게 처리할 지에 대해 정의하는 것이다.

이 선언적 에러처리를 통해 컴포넌트 내에서 에러처리 로직을 구현할 필요가 없어지고, 이외의 비즈니스 로직에만 집중하여 개발할 수 있게 된다.

이를 통해 코드의 유지보수성도 향상되는 효과를 얻을 수 있다.

 

이유2) 에러를 한곳에서 처리할 수 있다.

에러를 중앙에서 포착하고 처리하기 때문에 에러 로깅과 같은 에러 처리 작업을 일관되게 적용할 수 있다. 예를 들어 로그를 통해 어떤 에러가 발생했는지, 어디서 발생했는 지를 좀 더 명확하게 파악할 수 있다.

 

 

📍 새로운 에러핸들링 

react-error-boundary 라이브러리를 사용하여 함수형으로 에러 바운더리를 적용하였다.

 

에러 폴백 컴포넌트를 만들고 ErrorBoundary 컴포넌트로 최상위 컴포넌트를 감싸 전역적으로 에러를 포착할 수 있도록 설정한다.

import { FallbackProps } from "react-error-boundary";
import CustomError from "./CustomError";

const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {

  return (
    <CustomError
      mainText={error.name}
      subText={error.message}
      resetErrorBoundary={resetErrorBoundary}
    />
  );
};

export default ErrorFallback;
<ErrorBoundary FallbackComponent={ErrorFallback}>
  <Router />
</ErrorBoundary>;

폴백 컴포넌트 내부에서 에러코드나 이름에 따라 적합한 에러 페이지나 에러 모달을 띄우도록 커스텀할 수 있다.

 

예외적으로 에러 바운더리가 포착하지 못하는 에러도 존재한다.

- 이벤트 핸들러 에러
- 비동기적 코드 (ex. setTimeout)
- SSR
- 에러 경계 자체에서 발생하는 에러

이에 따르면 api 통신에서 발생하는 에러는 에러 바운더리에서 포착할 수 없었고, 이 문제는 리액트 쿼리의 queryClient 옵션을 통해 해결할 수 있었다.

 

에러 바운더리가 리액트 쿼리 에러를 포착하도록 하기 위해 throwOnError 옵션을 사용했다. 이 옵션을 통해 쿼리 요청 중 에러가 발생하면 해당 에러를 throw 하여 에러 바운더리로 전파할 수 있었다.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: true,
    },
  },
});

 

📍 더 생각해볼 점

최상위에서 에러 바운더리를 사용해서 모든 에러를 제어하는 게 정답인가 하는 의문이 들었다. 개발자 입장에서는 에러 처리를 한곳에서 관리할 수 있기 때문에 DX가 향상될 수 있지만 사용자 입장에서는 지나치게 통일된 에러 처리가 오히려 불편할 수도 있겠단 생각을 했다.

 

예를 들어 api 에러 같은 경우 전체 에러페이지나 에러 모달을 렌더링하는 것보다 부분적인 에러 컴포넌트를 사용해 에러 문구를 보여주는 것이 레이아웃 변동이 적기 때문에 UX가 더 개선되지 않을까 싶은 느낌..

 

예시로 든 상황에서는 일부 개별적인 에러 핸들링을 위하여 리액트 쿼리의 onError 옵션을 사용할 수도 있긴 하지만 그렇게 되면 또 한곳에서 처리하기가 번거로워지는 것 같고.. 

 

사용자 입장과 여러 방안을 좀 더 신경써서 에러 핸들링을 해봐야 할 것 같다