이번 시간에는 에러를 catch해주는 ErrorBoundary 컴포넌트에 대해 알아보겠습니다.
에러 바운더리는 다음과 같이 Class로 선언합니다. 제가 리액트를 하면서 유일하게 클래스 컴포넌트를 쓸 때가 이 때입니다. 리액트 공식 문서의 예제를 가져왔습니다.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) { // 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
return { hasError: true };
}
componentDidCatch(error, errorInfo) { // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
logErrorToMyService(error, errorInfo);
}
render() { if (this.state.hasError) { // 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
getDerivedStateFromError와 componentDidCatch가 에러 바운더리의 주요 특징입니다. getDerivedStateFromError는 에러 발생 시 state를 변경할 수 있습니다. 위 예제에서는 간단히 hasError을 true로 변경했지만, 에러의 종류에 따라서 분기 처리를 할 수도 있겠죠. componentDidCatch는 에러가 발생할 때 실행되는 라이프사이클입니다. 에러를 처리하는 다양한 동작과 state를 바꾸는 것 까지도 여기서 처리하면 됩니다.
위 코드에서는 에러가 발생하면 아예 통째로 Something went wrong이라는 글자로 대체되는데 render 부분은 사용하기 나름이므로 여러 응용이 가능합니다.
render() {
<>
{this.props.children}
<h1>Something went wrong</h1>;
</>
}
이런 식으로 기존 컴포넌트 아래에 에러 메시지를 추가할 수도 있고요.
에러 바운더리 적용은 다음 과 같이 합니다.
<ErrorBoundary>
<App />
</ErrorBoundary>
이렇게 에러 바운더리로 감싸면 됩니다. 그러면 에러 바운더리의 자식은 App 컴포넌트 내부에서 발생한 에러는 모두 에러 바운더리 컴포넌트가 처리합니다. 이를 반대로 말하면, 에러 바운더리의 자식이 아닌 컴포넌트는 에러 바운더리가 처리하지 못 한다는 뜻입니다.
에러 바운더리는 여러 개를 만들 수 있으므로 처리하고자 하는 에러의 범위에 맞게 생성하면 됩니다.
<ErrorBoundary> // App1이나 App2에서 에러가 발생하는 것을 모두 다 처리
<App1 />
<App2 />
</ErrorBoundary>
<ErrorBoundary> // App1 에러만 처리
<App1 />
</ErrorBoundary>
<ErrorBoundary> // App2 에러만 처리
<App2 />
</ErrorBoundary>
참고로 에러 바운더리는 비동기 함수, 이벤트 리스너, 서버사이드렌더링, 에러 바운더리 자체에서 발생하는 에러는 잡지 못하므로 이런 에러들은 따로 try/catch로 잡으셔야 합니다. 비동기 함수는 다들 주의깊게 사용하는데 이벤트 리스너에서 throw하는 실수를 많이 저지르게 되는 것 같습니다.
에러가 발생한 이후 다시 화면을 원상복구하거나 실패한 작업을 재시도하고 싶을 때가 있습니다. "다시 시도"나 "새로고침" 버튼을 누를 때죠. 그럴 때는 react-error-boundary 를 사용하는 것이 좋습니다. 기본 에러 바운더리에 필요한 기능을 잘 추가해두었습니다.