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