Error Boundary
이번 주 플젝 중 백엔드 팀원들이 에러 핸들러를 만들면서 고생하는걸 지켜봤다. 클라이언트에서는 서버에서 보내주는 상태 코드를 처리하거나 자체 에러를 핸들링 할 때 어떻게 해야 할까?
Component – React
The library for web and native user interfaces
react.dev
레거시로 분류되어 있는 error boundary.. 그러나 카카오페이지에서 사용한다는걸 보고 그냥 쓰기로 했다 ㅎ
리액트는 렌더링 중에 에러가 생기면 화면에서 UI를 지워버린다. 이걸 "컴포넌트가 깨진다"고들 표현한다. 즉, 에러가 생긴 컴포넌트만 언마운트 되는 게 아니라 모든 컴포넌트가 언마운트 되는 것이다.
잘못된 정보를 사용자에게 보여 주는 것보다는 아무것도 보여주지 않는 것이 낫다는 것인데, 문제는 사용자 경험이 나빠진다는 것이다. 이 문제를 해결하기 위해 error boundary가 등장했다.
React 공식 문서 피셜로 error boundary는 클래스형으로 작성되어야 하며, 함수형으로 쓸 방법은 없다. (대신 react-error-boundary와 같은 대안을 추천하고 있다.)
클래스형으로 작성되어야 한다고 코드 전체가 클래스형이어야 한다는 건 아니고 error boundary만 클래스형으로 만들어서 감싸주면 된다.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 다음 렌더링에서 폴백 UI가 보이도록 상태 업데이트
return { hasError: true };
}
componentDidCatch(error, info) {
// 에러를 기록할 수도 있음
logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// 폴백 UI 커스텀해서 렌더링 할 수 있음
return this.props.fallback;
}
return this.props.children;
}
}
<ErrorBoundary fallback={<p>Something went wrong</p>}>
<Profile />
</ErrorBoundary>
에러가 발생할 수 있는 UI의 일부분을 error boundary로 감싸서 빈 화면이 보이는 것을 예방할 수 있다. error boundary는 깨진 부분 대신 fallback UI를 표시한다.
위 이미지처럼 부분적으로 폴백 UI를 띄워 사용자에게 에러가 발생했음을 알리고, 에러가 발생하지 않은 나머지 컴포넌트는 보여줄 수 있다.
error boundary는 하위 트리에서 렌더링 중에 throw된 에러를 catch하도록 동작한다. 해당 error boundary에서 에러 처리를 실패하면 가장 가까운 error boundary에서 catch한다. 위 이미지의 출처인 카카오페이지에서는 api error boundary로 컴포넌트들을 감싸고, 제일 위에 global error boundary를 둬서 rethrow된 에러를 처리하도록 구현했다.
<GlobalErrorBoundary>
<ApiErrorBoundary>
<ContentProductListFetcher>
<ContentProductListContainer />
</ContentProductListFetcher>
</ApiErrorBoundary>
<ApiErrorBoundary>
<ContentRecommendFetcher>
<ContentHomeRecommendContainer />
</ContentRecommendFetcher>
</ApiErrorBoundary>
<ApiErrorBoundary>
<CommentFetcher>
<CommentContainer />
</CommentFetcher>
</ApiErrorBoundary>
</GlobalErrorBoundary>
// 출처: https://fe-developers.kakaoent.com/2022/221110-error-boundary/
global error boundary는 루트 레벨에서 에러를 처리하는데, 서버 점검과 같이 페이지 전체에 에러를 보여줘야 하는 경우나 하위 error boundary에서 처리하지 못한 에러를 catch하는 용도다.
설명에서 짐작할 수 있듯이 error boundary는 루트에 위치할 필요도 없고 모든 컴포넌트를 감싸지 않아도 된다. 개발자의 의도에 따라 배치할 수 있다는 것이다.
단, error boundary가 처리하지 않는 예외 상황이 있다.
- 이벤트 핸들러
- 비동기적 코드 (setTimeout 등)
- 서버사이드 렌더링
- 자식이 아닌 Error Boundary 자체에서 발생한 예외
error boundary는 렌더링 중에 발생하는 예외만 처리한다. 이벤트 핸들러에서 에러 처리를 하려면 try-catch로 직접 처리해야 한다.
const throwError = () => {
try {
throw new Error("Error!");
} catch (err) {
console.error(err);
}
};
앞서 언급한 react-error-boundary에서는 이벤트 핸들러에서 직접 try-catch하는 것을 쉽게 해주는 useErrorHandler 기능을 제공한다.
// App.js
import {ErrorBoundary} from 'react-error-boundary';
const ErrorFallback = (err) => {
return(<div>폴백 UI</div>)
}
function App() {
return (
<div className="App">
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
</div>
);
}
export default App;
// MyComponent
import { useErrorHandler } from 'react-error-boundary';
...
const handleError = useErrorHandler();
...
try {
...
}
catch(err) {
handleError(err);
}
...
그동안 신경쓰지 못했던 사용자 경험을 위한 기술들을 공부하기로 결심했다. 이번 프로젝트에 잘 적용해 봐야겠다.